From 94bbf2911e915d793c4065aa74cecc721df0bdee Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 10 Aug 2024 07:22:59 +1000 Subject: [PATCH 01/72] Bump version --- RELEASES.md | 15 +++++++++++++++ nautilus_core/Cargo.lock | 26 +++++++++++++------------- nautilus_core/Cargo.toml | 2 +- poetry.lock | 12 ++++++------ pyproject.toml | 4 ++-- version.json | 2 +- 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 263ff8c89c24..e0681787d805 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,18 @@ +# NautilusTrader 1.199.0 Beta + +Released on TBD (UTC). + +### Enhancements +None + +### Breaking Changes +None + +### Fixes +None + +--- + # NautilusTrader 1.198.0 Beta Released on 9th August 2024 (UTC). diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index 6627a63bef86..73209f96afbc 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -2564,7 +2564,7 @@ dependencies = [ [[package]] name = "nautilus-adapters" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "chrono", @@ -2594,7 +2594,7 @@ dependencies = [ [[package]] name = "nautilus-backtest" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "cbindgen", @@ -2616,7 +2616,7 @@ dependencies = [ [[package]] name = "nautilus-cli" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "clap 4.5.14", @@ -2633,7 +2633,7 @@ dependencies = [ [[package]] name = "nautilus-common" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "bytes", @@ -2662,7 +2662,7 @@ dependencies = [ [[package]] name = "nautilus-core" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "bytes", @@ -2683,7 +2683,7 @@ dependencies = [ [[package]] name = "nautilus-data" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "chrono", @@ -2709,7 +2709,7 @@ dependencies = [ [[package]] name = "nautilus-execution" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "criterion", @@ -2733,7 +2733,7 @@ dependencies = [ [[package]] name = "nautilus-indicators" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "log", @@ -2746,7 +2746,7 @@ dependencies = [ [[package]] name = "nautilus-infrastructure" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "async-stream", @@ -2774,7 +2774,7 @@ dependencies = [ [[package]] name = "nautilus-model" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "cbindgen", @@ -2802,7 +2802,7 @@ dependencies = [ [[package]] name = "nautilus-network" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "axum", @@ -2828,7 +2828,7 @@ dependencies = [ [[package]] name = "nautilus-persistence" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "binary-heap-plus", @@ -2852,7 +2852,7 @@ dependencies = [ [[package]] name = "nautilus-pyo3" -version = "0.28.0" +version = "0.29.0" dependencies = [ "nautilus-adapters", "nautilus-common", diff --git a/nautilus_core/Cargo.toml b/nautilus_core/Cargo.toml index aad7837fbb9a..d4796075e357 100644 --- a/nautilus_core/Cargo.toml +++ b/nautilus_core/Cargo.toml @@ -19,7 +19,7 @@ members = [ [workspace.package] rust-version = "1.80.1" -version = "0.28.0" +version = "0.29.0" edition = "2021" authors = ["Nautech Systems "] description = "A high-performance algorithmic trading platform and event-driven backtester" diff --git a/poetry.lock b/poetry.lock index ab82f092bc78..7f2264c4735e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1303,13 +1303,13 @@ files = [ [[package]] name = "numpydoc" -version = "1.7.0" +version = "1.8.0" description = "Sphinx extension to support docstrings in Numpy format" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "numpydoc-1.7.0-py3-none-any.whl", hash = "sha256:5a56419d931310d79a06cfc2a126d1558700feeb9b4f3d8dcae1a8134be829c9"}, - {file = "numpydoc-1.7.0.tar.gz", hash = "sha256:866e5ae5b6509dcf873fc6381120f5c31acf13b135636c1a81d68c166a95f921"}, + {file = "numpydoc-1.8.0-py3-none-any.whl", hash = "sha256:72024c7fd5e17375dec3608a27c03303e8ad00c81292667955c6fea7a3ccf541"}, + {file = "numpydoc-1.8.0.tar.gz", hash = "sha256:022390ab7464a44f8737f79f8b31ce1d3cfa4b4af79ccaa1aac5e8368db587fb"}, ] [package.dependencies] @@ -1319,7 +1319,7 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] developer = ["pre-commit (>=3.3)", "tomli"] -doc = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pydata-sphinx-theme (>=0.13.3)", "sphinx (>=7)"] +doc = ["intersphinx-registry", "matplotlib (>=3.5)", "numpy (>=1.22)", "pydata-sphinx-theme (>=0.13.3)", "sphinx (>=7)"] test = ["matplotlib", "pytest", "pytest-cov"] [[package]] @@ -2398,4 +2398,4 @@ ib = ["async-timeout", "defusedxml", "nautilus_ibapi"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "5e63477164b813049a19c7e3a729259e0bea658bb35122528ab55f3c02fff9ac" +content-hash = "f2fdd11db584bedddd37adcbeec0be1ef10993cfb5db1f750649b058182d9a6d" diff --git a/pyproject.toml b/pyproject.toml index b7dd9b23d928..14fa58a64c2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nautilus_trader" -version = "1.198.0" +version = "1.199.0" description = "A high-performance algorithmic trading platform and event-driven backtester" authors = ["Nautech Systems "] license = "LGPL-3.0-or-later" @@ -106,7 +106,7 @@ pytest-xdist = { version = "^3.6.1", extras = ["psutil"] } optional = true [tool.poetry.group.docs.dependencies] -numpydoc = "^1.7.0" +numpydoc = "^1.8.0" linkify-it-py = "^2.0.3" myst-parser = "^3.0.1" sphinx_comments = "^0.0.3" diff --git a/version.json b/version.json index df81630c9d01..bbf13bc92753 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, "label": "", - "message": "v1.198.0", + "message": "v1.199.0", "color": "orange" } From 50dc0fd46aa6bde360475d5ef335cfb927b42213 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 10 Aug 2024 07:44:28 +1000 Subject: [PATCH 02/72] Standardize test naming --- nautilus_core/backtest/src/matching_engine.rs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index 37e5a1c496b2..289ca1abc371 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -53,6 +53,7 @@ use nautilus_model::{ }; use ustr::Ustr; +/// Configuration for [`OrderMatchingEngine`] instances. #[derive(Debug, Clone)] pub struct OrderMatchingEngineConfig { pub bar_execution: bool, @@ -115,7 +116,6 @@ pub struct OrderMatchingEngine { execution_count: usize, } -// TODO: we'll probably be changing the `FillModel` (don't add for now) impl OrderMatchingEngine { /// Creates a new [`OrderMatchingEngine`] instance. #[allow(clippy::too_many_arguments)] @@ -212,7 +212,7 @@ impl OrderMatchingEngine { self.core.order_exists(client_order_id) } - // -- DATA PROCESSING ----------------------------------------------------- + // -- DATA PROCESSING ------------------------------------------------------------------------- /// Process the venues market for the given order book delta. pub fn process_order_book_delta(&mut self, delta: &OrderBookDelta) { @@ -221,7 +221,8 @@ impl OrderMatchingEngine { self.book.apply_delta(delta); } - // -- TRADING COMMANDS ---------------------------------------------------- + // -- TRADING COMMANDS ------------------------------------------------------------------------ + #[allow(clippy::needless_return)] pub fn process_order(&mut self, order: &OrderAny, account_id: AccountId) { // enter the scope where you will borrow a cache @@ -850,7 +851,8 @@ mod tests { static ATOMIC_TIME: LazyLock = LazyLock::new(|| AtomicTime::new(true, UnixNanos::default())); - // -- FIXTURES --------------------------------------------------------------------------- + // -- FIXTURES -------------------------------------------------------------------------------- + #[fixture] fn msgbus() -> MessageBus { MessageBus::default() @@ -889,7 +891,7 @@ mod tests { InstrumentAny::FuturesContract(futures_contract_es(Some(activation), Some(expiration))) } - // -- HELPERS --------------------------------------------------------------------------- + // -- HELPERS --------------------------------------------------------------------------------- fn get_order_matching_engine( instrument: InstrumentAny, @@ -925,9 +927,10 @@ mod tests { .get_messages() } - // -- TESTS --------------------------------------------------------------------------- + // -- TESTS ----------------------------------------------------------------------------------- + #[rstest] - fn test_order_matching_engine_instrument_already_expired( + fn test_process_order_when_instrument_already_expired( mut msgbus: MessageBus, order_event_handler: ShareableMessageHandler, account_id: AccountId, @@ -965,7 +968,7 @@ mod tests { } #[rstest] - fn test_order_matching_engine_instrument_not_active( + fn test_process_order_when_instrument_not_active( mut msgbus: MessageBus, order_event_handler: ShareableMessageHandler, account_id: AccountId, @@ -1016,7 +1019,7 @@ mod tests { } #[rstest] - fn test_order_matching_engine_wrong_order_quantity_precision( + fn test_process_order_when_invalid_quantity_precision( mut msgbus: MessageBus, order_event_handler: ShareableMessageHandler, account_id: AccountId, @@ -1053,7 +1056,7 @@ mod tests { } #[rstest] - fn test_order_matching_engine_wrong_order_price_precision( + fn test_process_order_when_invalid_price_precision( mut msgbus: MessageBus, order_event_handler: ShareableMessageHandler, account_id: AccountId, @@ -1092,7 +1095,7 @@ mod tests { } #[rstest] - fn test_order_matching_engine_wrong_order_trigger_price_precision( + fn test_process_order_when_invalid_trigger_price_precision( mut msgbus: MessageBus, order_event_handler: ShareableMessageHandler, account_id: AccountId, @@ -1132,7 +1135,7 @@ mod tests { } #[rstest] - fn test_order_matching_engine_error_shorting_equity_without_margin_account( + fn test_process_order_when_shorting_equity_without_margin_account( mut msgbus: MessageBus, order_event_handler: ShareableMessageHandler, account_id: AccountId, @@ -1175,7 +1178,7 @@ mod tests { } #[rstest] - fn test_order_matching_engine_reduce_only_error( + fn test_process_order_when_invalid_reduce_only( mut msgbus: MessageBus, order_event_handler: ShareableMessageHandler, account_id: AccountId, @@ -1227,7 +1230,7 @@ mod tests { } #[rstest] - fn test_order_matching_engine_contingent_orders_errors( + fn test_process_order_when_invalid_contingent_orders( mut msgbus: MessageBus, order_event_handler: ShareableMessageHandler, account_id: AccountId, From f7df72e26255b83a7d602a0460b3948984b3605a Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 10 Aug 2024 09:27:55 +1000 Subject: [PATCH 03/72] Use Ustr for messaging endpoint registration --- nautilus_core/backtest/src/matching_engine.rs | 16 ++++++++-------- nautilus_core/common/src/msgbus/handler.rs | 2 +- nautilus_core/common/src/msgbus/mod.rs | 18 +++++++++--------- nautilus_core/common/src/python/msgbus.rs | 4 ++-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index 289ca1abc371..52e942b2d864 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -940,7 +940,7 @@ mod tests { // Register saving message handler to exec engine endpoint msgbus.register( - msgbus.switchboard.exec_engine_process.as_str(), + msgbus.switchboard.exec_engine_process, order_event_handler.clone(), ); @@ -991,7 +991,7 @@ mod tests { // Register saving message handler to exec engine endpoint msgbus.register( - msgbus.switchboard.exec_engine_process.as_str(), + msgbus.switchboard.exec_engine_process, order_event_handler.clone(), ); @@ -1028,7 +1028,7 @@ mod tests { ) { // Register saving message handler to exec engine endpoint msgbus.register( - msgbus.switchboard.exec_engine_process.as_str(), + msgbus.switchboard.exec_engine_process, order_event_handler.clone(), ); @@ -1065,7 +1065,7 @@ mod tests { ) { // Register saving message handler to exec engine endpoint msgbus.register( - msgbus.switchboard.exec_engine_process.as_str(), + msgbus.switchboard.exec_engine_process, order_event_handler.clone(), ); @@ -1104,7 +1104,7 @@ mod tests { ) { // Register saving message handler to exec engine endpoint msgbus.register( - msgbus.switchboard.exec_engine_process.as_str(), + msgbus.switchboard.exec_engine_process, order_event_handler.clone(), ); @@ -1145,7 +1145,7 @@ mod tests { let instrument = InstrumentAny::Equity(equity_aapl); // Register saving message handler to exec engine endpoint msgbus.register( - msgbus.switchboard.exec_engine_process.as_str(), + msgbus.switchboard.exec_engine_process, order_event_handler.clone(), ); @@ -1187,7 +1187,7 @@ mod tests { ) { // Register saving message handler to exec engine endpoint msgbus.register( - msgbus.switchboard.exec_engine_process.as_str(), + msgbus.switchboard.exec_engine_process, order_event_handler.clone(), ); @@ -1239,7 +1239,7 @@ mod tests { ) { // Register saving message handler to exec engine endpoint msgbus.register( - msgbus.switchboard.exec_engine_process.as_str(), + msgbus.switchboard.exec_engine_process, order_event_handler.clone(), ); diff --git a/nautilus_core/common/src/msgbus/handler.rs b/nautilus_core/common/src/msgbus/handler.rs index d90547d393ad..12ba3e08ae4c 100644 --- a/nautilus_core/common/src/msgbus/handler.rs +++ b/nautilus_core/common/src/msgbus/handler.rs @@ -38,5 +38,5 @@ impl From> for ShareableMessageHandler { } } -// Message handlers are not expected to be sent across thread boundaries +// SAFETY: Message handlers cannot be sent across thread boundaries unsafe impl Send for ShareableMessageHandler {} diff --git a/nautilus_core/common/src/msgbus/mod.rs b/nautilus_core/common/src/msgbus/mod.rs index c64fb80747ff..347c1941a0e5 100644 --- a/nautilus_core/common/src/msgbus/mod.rs +++ b/nautilus_core/common/src/msgbus/mod.rs @@ -244,15 +244,15 @@ impl MessageBus { } /// Registers the given `handler` for the `endpoint` address. - pub fn register(&mut self, endpoint: &str, handler: ShareableMessageHandler) { + pub fn register(&mut self, endpoint: Ustr, handler: ShareableMessageHandler) { // Updates value if key already exists - self.endpoints.insert(Ustr::from(endpoint), handler); + self.endpoints.insert(endpoint, handler); } /// Deregisters the given `handler` for the `endpoint` address. - pub fn deregister(&mut self, endpoint: &str) { + pub fn deregister(&mut self, endpoint: &Ustr) { // Removes entry if it exists for endpoint - self.endpoints.shift_remove(&Ustr::from(endpoint)); + self.endpoints.shift_remove(endpoint); } /// Subscribes the given `handler` to the `topic`. @@ -494,7 +494,7 @@ mod tests { #[rstest] fn test_regsiter_endpoint() { let mut msgbus = stub_msgbus(); - let endpoint = "MyEndpoint"; + let endpoint = Ustr::from("MyEndpoint"); let handler_id = Ustr::from("1"); let handler = get_stub_shareable_handler(handler_id); @@ -502,7 +502,7 @@ mod tests { msgbus.register(endpoint, handler); assert_eq!(msgbus.endpoints(), vec!["MyEndpoint".to_string()]); - assert!(msgbus.get_endpoint(&Ustr::from(endpoint)).is_some()); + assert!(msgbus.get_endpoint(&endpoint).is_some()); } #[rstest] @@ -513,7 +513,7 @@ mod tests { let handler_id = Ustr::from("1"); let handler = get_call_check_shareable_handler(handler_id); - msgbus.register(endpoint.as_str(), handler.clone()); + msgbus.register(endpoint, handler.clone()); assert!(msgbus.get_endpoint(&endpoint).is_some()); // check if the handler called variable is false @@ -541,13 +541,13 @@ mod tests { #[rstest] fn test_deregsiter_endpoint() { let mut msgbus = stub_msgbus(); - let endpoint = "MyEndpoint"; + let endpoint = Ustr::from("MyEndpoint"); let handler_id = Ustr::from("1"); let handler = get_stub_shareable_handler(handler_id); msgbus.register(endpoint, handler); - msgbus.deregister(endpoint); + msgbus.deregister(&endpoint); assert!(msgbus.endpoints().is_empty()); } diff --git a/nautilus_core/common/src/python/msgbus.rs b/nautilus_core/common/src/python/msgbus.rs index e1d0fa7eadb4..11779decaadb 100644 --- a/nautilus_core/common/src/python/msgbus.rs +++ b/nautilus_core/common/src/python/msgbus.rs @@ -62,7 +62,7 @@ impl MessageBus { pub fn register_py(&mut self, endpoint: &str, handler: PythonMessageHandler) { // Updates value if key already exists let handler = ShareableMessageHandler(Rc::new(handler)); - self.register(endpoint, handler); + self.register(Ustr::from(endpoint), handler); } /// Subscribes the given `handler` to the `topic`. @@ -119,6 +119,6 @@ impl MessageBus { #[pyo3(name = "deregister")] pub fn deregister_py(&mut self, endpoint: &str) { // Removes entry if it exists for endpoint - self.deregister(endpoint); + self.deregister(&Ustr::from(endpoint)); } } From 3ab9d34ba25acc839570bf6d4a73f22a44a1e5f3 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 10 Aug 2024 09:28:24 +1000 Subject: [PATCH 04/72] Add DataEngine tests in Rust --- nautilus_core/data/src/engine/mod.rs | 336 +++++++++++++++++++++++++-- 1 file changed, 317 insertions(+), 19 deletions(-) diff --git a/nautilus_core/data/src/engine/mod.rs b/nautilus_core/data/src/engine/mod.rs index caf6a7f50396..05775122168f 100644 --- a/nautilus_core/data/src/engine/mod.rs +++ b/nautilus_core/data/src/engine/mod.rs @@ -275,7 +275,6 @@ impl DataEngine { /// Send a [`DataRequest`] to an endpoint that must be a data client implementation. pub fn execute(&mut self, msg: &dyn Any) { - // TODO: log error if let Some(cmd) = msg.downcast_ref::() { if let Some(client) = self.clients.get_mut(&cmd.client_id) { client.execute(cmd.clone()); @@ -285,6 +284,8 @@ impl DataEngine { cmd.client_id ); } + } else { + log::error!("Invalid message type received: {msg:?}"); } } @@ -594,40 +595,261 @@ impl MessageHandler for SubscriptionCommandHandler { mod tests { use indexmap::indexmap; use nautilus_common::{ - clock::TestClock, messages::data::Action, msgbus::handler::ShareableMessageHandler, + clock::TestClock, + messages::data::Action, + msgbus::{handler::ShareableMessageHandler, switchboard::MessagingSwitchboard}, }; use nautilus_core::{nanos::UnixNanos, uuid::UUID4}; use nautilus_model::{ + enums::BookType, identifiers::TraderId, instruments::{currency_pair::CurrencyPair, stubs::audusd_sim}, }; - use rstest::rstest; + use rstest::*; use super::*; use crate::mocks::MockDataClient; - #[rstest] - fn test_execute_subscribe_instruments(audusd_sim: CurrencyPair) { - // TODO: Cleanup test and provide more stubs - let trader_id = TraderId::from("TESTER-001"); - let clock = Box::new(TestClock::new()); - let cache = Rc::new(RefCell::new(Cache::default())); - let msgbus = Rc::new(RefCell::new(MessageBus::new( + #[fixture] + fn trader_id() -> TraderId { + TraderId::default() + } + + #[fixture] + fn client_id() -> ClientId { + ClientId::default() + } + + #[fixture] + fn venue() -> Venue { + Venue::default() + } + + #[fixture] + fn clock() -> Box { + Box::new(TestClock::new()) + } + + #[fixture] + fn cache() -> Rc> { + Rc::new(RefCell::new(Cache::default())) + } + + #[fixture] + fn msgbus(trader_id: TraderId) -> Rc> { + Rc::new(RefCell::new(MessageBus::new( trader_id, UUID4::new(), None, None, - ))); - let switchboard = msgbus.borrow().switchboard.clone(); + ))) + } + + #[fixture] + fn switchboard(msgbus: Rc>) -> MessagingSwitchboard { + msgbus.borrow().switchboard.clone() + } + + #[fixture] + fn data_engine( + clock: Box, + cache: Rc>, + msgbus: Rc>, + ) -> Rc> { let data_engine = DataEngine::new(clock, cache.clone(), msgbus.clone(), None); - let data_engine = Rc::new(RefCell::new(data_engine)); + Rc::new(RefCell::new(data_engine)) + } + + // TODO: For some reason using this fixture causes the tests to fail? + #[fixture] + fn subscription_handler( + data_engine: Rc>, + switchboard: MessagingSwitchboard, + ) -> ShareableMessageHandler { + ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: switchboard.data_engine_execute, + data_engine: data_engine.clone(), + })) + } - let client_id = ClientId::from("SIM"); - let venue = Venue::from("SIM"); + #[fixture] + fn data_client( + client_id: ClientId, + venue: Venue, + cache: Rc>, + msgbus: Rc>, + clock: Box, + ) -> DataClientAdapter { let client = Box::new(MockDataClient::new(cache, msgbus.clone(), client_id, venue)); + DataClientAdapter::new(client_id, venue, client, clock) + } + + #[rstest] + fn test_execute_subscribe_custom_data( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let data_type = DataType::new(stringify!(String), None); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type.clone(), + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + assert!(data_engine + .borrow() + .subscribed_custom_data() + .contains(&data_type)); + } + + #[rstest] + fn test_execute_subscribe_order_book_deltas( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id.to_string(), + "book_type".to_string() => BookType::L3_MBO.to_string(), + }; + let data_type = DataType::new(stringify!(OrderBookDelta), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type.clone(), + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + assert!(data_engine + .borrow() + .subscribed_order_book_deltas() + .contains(&audusd_sim.id)); + } + + #[rstest] + fn test_execute_subscribe_order_book_snapshots( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id.to_string(), + "book_type".to_string() => BookType::L2_MBP.to_string(), + }; + let data_type = DataType::new(stringify!(OrderBookDeltas), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type.clone(), + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); - let client = DataClientAdapter::new(client_id, venue, client, Box::new(TestClock::new())); - data_engine.borrow_mut().register_client(client, None); + assert!(data_engine + .borrow() + .subscribed_order_book_snapshots() + .contains(&audusd_sim.id)); + } + + #[rstest] + fn test_execute_subscribe_instrument( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id.to_string(), + }; + let data_type = DataType::new(stringify!(InstrumentAny), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + assert!(data_engine + .borrow() + .subscribed_instruments() + .contains(&audusd_sim.id)); + } + + #[rstest] + fn test_execute_subscribe_quote_ticks( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); let metadata = indexmap! { "instrument_id".to_string() => audusd_sim.id.to_string(), @@ -644,10 +866,10 @@ mod tests { let endpoint = switchboard.data_engine_execute; let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { - id: switchboard.data_engine_process, + id: endpoint, data_engine: data_engine.clone(), })); - msgbus.borrow_mut().register(endpoint.as_str(), handler); + msgbus.borrow_mut().register(endpoint, handler); msgbus.borrow().send(&endpoint, &cmd as &dyn Any); assert!(data_engine @@ -655,4 +877,80 @@ mod tests { .subscribed_quote_ticks() .contains(&audusd_sim.id)); } + + #[rstest] + fn test_execute_subscribe_trade_ticks( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id.to_string(), + }; + let data_type = DataType::new(stringify!(TradeTick), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + assert!(data_engine + .borrow() + .subscribed_trade_ticks() + .contains(&audusd_sim.id)); + } + + #[rstest] + fn test_execute_subscribe_bars( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let bar_type = BarType::from("AUDUSD.SIM-1-MINUTE-LAST-INTERNAL"); + let metadata = indexmap! { + "bar_type".to_string() => bar_type.to_string(), + }; + let data_type = DataType::new(stringify!(Bar), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + assert!(data_engine.borrow().subscribed_bars().contains(&bar_type)); + } } From b5d81619d590c4e5c96652507fa54dca6a04fdd3 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 10 Aug 2024 10:48:58 +1000 Subject: [PATCH 05/72] Refine messaging and switchboard --- nautilus_core/common/src/msgbus/mod.rs | 27 +- .../common/src/msgbus/switchboard.rs | 195 ++++++++++++++ nautilus_core/common/src/python/msgbus.rs | 4 +- nautilus_core/data/src/engine/mod.rs | 243 ++++++++++-------- nautilus_core/data/src/engine/runner.rs | 6 +- nautilus_core/model/src/data/deltas.rs | 2 +- nautilus_core/model/src/data/mod.rs | 2 +- 7 files changed, 353 insertions(+), 126 deletions(-) diff --git a/nautilus_core/common/src/msgbus/mod.rs b/nautilus_core/common/src/msgbus/mod.rs index 347c1941a0e5..3fcb39054964 100644 --- a/nautilus_core/common/src/msgbus/mod.rs +++ b/nautilus_core/common/src/msgbus/mod.rs @@ -258,11 +258,10 @@ impl MessageBus { /// Subscribes the given `handler` to the `topic`. pub fn subscribe( &mut self, - topic: &str, + topic: Ustr, handler: ShareableMessageHandler, priority: Option, ) { - let topic = Ustr::from(topic); let sub = Subscription::new(topic, handler, priority); if self.subscriptions.contains_key(&sub) { @@ -287,8 +286,8 @@ impl MessageBus { } /// Unsubscribes the given `handler` from the `topic`. - pub fn unsubscribe(&mut self, topic: &str, handler: ShareableMessageHandler) { - let sub = Subscription::new(Ustr::from(topic), handler, None); + pub fn unsubscribe(&mut self, topic: Ustr, handler: ShareableMessageHandler) { + let sub = Subscription::new(topic, handler, None); self.subscriptions.shift_remove(&sub); } @@ -349,9 +348,8 @@ impl MessageBus { } /// Publish a message to a topic. - pub fn publish(&self, topic: &str, message: &dyn Any) { - let topic = Ustr::from(topic); - let matching_subs = self.matching_subscriptions(&topic); + pub fn publish(&self, topic: &Ustr, message: &dyn Any) { + let matching_subs = self.matching_subscriptions(topic); for sub in matching_subs { sub.handler.0.handle(message); @@ -555,21 +553,21 @@ mod tests { #[rstest] fn test_subscribe() { let mut msgbus = stub_msgbus(); - let topic = "my-topic"; + let topic = Ustr::from("my-topic"); let handler_id = Ustr::from("1"); let handler = get_stub_shareable_handler(handler_id); msgbus.subscribe(topic, handler, Some(1)); - assert!(msgbus.has_subscribers(topic)); - assert_eq!(msgbus.topics(), vec![topic]); + assert!(msgbus.has_subscribers(topic.as_str())); + assert_eq!(msgbus.topics(), vec![topic.as_str()]); } #[rstest] fn test_unsubscribe() { let mut msgbus = stub_msgbus(); - let topic = "my-topic"; + let topic = Ustr::from("my-topic"); let handler_id = Ustr::from("1"); let handler = get_stub_shareable_handler(handler_id); @@ -577,14 +575,14 @@ mod tests { msgbus.subscribe(topic, handler.clone(), None); msgbus.unsubscribe(topic, handler); - assert!(!msgbus.has_subscribers(topic)); + assert!(!msgbus.has_subscribers(topic.as_str())); assert!(msgbus.topics().is_empty()); } #[rstest] fn test_matching_subscriptions() { let mut msgbus = stub_msgbus(); - let topic = "my-topic"; + let topic = Ustr::from("my-topic"); let handler_id1 = Ustr::from("1"); let handler1 = get_stub_shareable_handler(handler_id1); @@ -602,8 +600,7 @@ mod tests { msgbus.subscribe(topic, handler2, None); msgbus.subscribe(topic, handler3, Some(1)); msgbus.subscribe(topic, handler4, Some(2)); - let topic_ustr = Ustr::from(topic); - let subs = msgbus.matching_subscriptions(&topic_ustr); + let subs = msgbus.matching_subscriptions(&topic); assert_eq!(subs.len(), 4); assert_eq!(subs[0].handler_id, handler_id4); diff --git a/nautilus_core/common/src/msgbus/switchboard.rs b/nautilus_core/common/src/msgbus/switchboard.rs index f883344a72e7..16bc19e3e943 100644 --- a/nautilus_core/common/src/msgbus/switchboard.rs +++ b/nautilus_core/common/src/msgbus/switchboard.rs @@ -13,6 +13,12 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- +use std::collections::HashMap; + +use nautilus_model::{ + data::{bar::BarType, DataType}, + identifiers::InstrumentId, +}; use ustr::Ustr; /// Represents a switchboard of built-in messaging endpoint names. @@ -22,6 +28,14 @@ pub struct MessagingSwitchboard { pub data_engine_process: Ustr, pub exec_engine_execute: Ustr, pub exec_engine_process: Ustr, + custom_topics: HashMap, + instrument_topics: HashMap, + delta_topics: HashMap, + deltas_topics: HashMap, + depth_topics: HashMap, + quote_topics: HashMap, + trade_topics: HashMap, + bar_topics: HashMap, } impl Default for MessagingSwitchboard { @@ -31,6 +45,187 @@ impl Default for MessagingSwitchboard { data_engine_process: Ustr::from("DataEngine.process"), exec_engine_execute: Ustr::from("ExecEngine.execute"), exec_engine_process: Ustr::from("ExecEngine.process"), + custom_topics: HashMap::new(), + instrument_topics: HashMap::new(), + delta_topics: HashMap::new(), + deltas_topics: HashMap::new(), + depth_topics: HashMap::new(), + quote_topics: HashMap::new(), + trade_topics: HashMap::new(), + bar_topics: HashMap::new(), } } } + +impl MessagingSwitchboard { + #[must_use] + pub fn get_custom_topic(&mut self, data_type: &DataType) -> Ustr { + *self + .custom_topics + .entry(data_type.clone()) + .or_insert_with(|| Ustr::from(&format!("data.{}", data_type.topic()))) + } + + #[must_use] + pub fn get_instrument_topic(&mut self, instrument_id: InstrumentId) -> Ustr { + *self + .instrument_topics + .entry(instrument_id) + .or_insert_with(|| { + Ustr::from(&format!( + "data.instrument.{}.{}", + instrument_id.venue, instrument_id.symbol + )) + }) + } + + #[must_use] + pub fn get_delta_topic(&mut self, instrument_id: InstrumentId) -> Ustr { + *self.delta_topics.entry(instrument_id).or_insert_with(|| { + Ustr::from(&format!( + "data.book.delta.{}.{}", + instrument_id.venue, instrument_id.symbol + )) + }) + } + + #[must_use] + pub fn get_deltas_topic(&mut self, instrument_id: InstrumentId) -> Ustr { + *self.deltas_topics.entry(instrument_id).or_insert_with(|| { + Ustr::from(&format!( + "data.book.snapshots.{}.{}", + instrument_id.venue, instrument_id.symbol + )) + }) + } + + #[must_use] + pub fn get_depth_topic(&mut self, instrument_id: InstrumentId) -> Ustr { + *self.depth_topics.entry(instrument_id).or_insert_with(|| { + Ustr::from(&format!( + "data.book.depth.{}.{}", + instrument_id.venue, instrument_id.symbol + )) + }) + } + + #[must_use] + pub fn get_quote_topic(&mut self, instrument_id: InstrumentId) -> Ustr { + *self.quote_topics.entry(instrument_id).or_insert_with(|| { + Ustr::from(&format!( + "data.quotes.{}.{}", + instrument_id.venue, instrument_id.symbol + )) + }) + } + + #[must_use] + pub fn get_trade_topic(&mut self, instrument_id: InstrumentId) -> Ustr { + *self.trade_topics.entry(instrument_id).or_insert_with(|| { + Ustr::from(&format!( + "data.trades.{}.{}", + instrument_id.venue, instrument_id.symbol + )) + }) + } + + #[must_use] + pub fn get_bar_topic(&mut self, bar_type: BarType) -> Ustr { + *self + .bar_topics + .entry(bar_type) + .or_insert_with(|| Ustr::from(&format!("data.bars.{bar_type}"))) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////////////// +#[cfg(test)] +mod tests { + use super::*; + use nautilus_model::{ + data::{bar::BarType, DataType}, + identifiers::InstrumentId, + }; + use rstest::*; + + #[fixture] + fn switchboard() -> MessagingSwitchboard { + MessagingSwitchboard::default() + } + + #[fixture] + fn instrument_id() -> InstrumentId { + InstrumentId::from("ESZ24.XCME") + } + + #[rstest] + fn test_get_custom_topic(mut switchboard: MessagingSwitchboard) { + let data_type = DataType::new("ExampleDataType", None); + let expected_topic = Ustr::from("data.ExampleDataType"); + let result = switchboard.get_custom_topic(&data_type); + assert_eq!(result, expected_topic); + assert!(switchboard.custom_topics.contains_key(&data_type)); + } + + #[rstest] + fn test_get_instrument_topic( + mut switchboard: MessagingSwitchboard, + instrument_id: InstrumentId, + ) { + let expected_topic = Ustr::from("data.instrument.XCME.ESZ24"); + let result = switchboard.get_instrument_topic(instrument_id); + assert_eq!(result, expected_topic); + assert!(switchboard.instrument_topics.contains_key(&instrument_id)); + } + + #[rstest] + fn test_get_delta_topic(mut switchboard: MessagingSwitchboard, instrument_id: InstrumentId) { + let expected_topic = Ustr::from("data.book.delta.XCME.ESZ24"); + let result = switchboard.get_delta_topic(instrument_id); + assert_eq!(result, expected_topic); + assert!(switchboard.delta_topics.contains_key(&instrument_id)); + } + + #[rstest] + fn test_get_deltas_topic(mut switchboard: MessagingSwitchboard, instrument_id: InstrumentId) { + let expected_topic = Ustr::from("data.book.snapshots.XCME.ESZ24"); + let result = switchboard.get_deltas_topic(instrument_id); + assert_eq!(result, expected_topic); + assert!(switchboard.deltas_topics.contains_key(&instrument_id)); + } + + #[rstest] + fn test_get_depth_topic(mut switchboard: MessagingSwitchboard, instrument_id: InstrumentId) { + let expected_topic = Ustr::from("data.book.depth.XCME.ESZ24"); + let result = switchboard.get_depth_topic(instrument_id); + assert_eq!(result, expected_topic); + assert!(switchboard.depth_topics.contains_key(&instrument_id)); + } + + #[rstest] + fn test_get_quote_topic(mut switchboard: MessagingSwitchboard, instrument_id: InstrumentId) { + let expected_topic = Ustr::from("data.quotes.XCME.ESZ24"); + let result = switchboard.get_quote_topic(instrument_id); + assert_eq!(result, expected_topic); + assert!(switchboard.quote_topics.contains_key(&instrument_id)); + } + + #[rstest] + fn test_get_trade_topic(mut switchboard: MessagingSwitchboard, instrument_id: InstrumentId) { + let expected_topic = Ustr::from("data.trades.XCME.ESZ24"); + let result = switchboard.get_trade_topic(instrument_id); + assert_eq!(result, expected_topic); + assert!(switchboard.trade_topics.contains_key(&instrument_id)); + } + + #[rstest] + fn test_get_bar_topic(mut switchboard: MessagingSwitchboard) { + let bar_type = BarType::from("ESZ24.XCME-1-MINUTE-LAST-INTERNAL"); + let expected_topic = Ustr::from(&format!("data.bars.{bar_type}")); + let result = switchboard.get_bar_topic(bar_type); + assert_eq!(result, expected_topic); + assert!(switchboard.bar_topics.contains_key(&bar_type)); + } +} diff --git a/nautilus_core/common/src/python/msgbus.rs b/nautilus_core/common/src/python/msgbus.rs index 11779decaadb..3ebe947a5948 100644 --- a/nautilus_core/common/src/python/msgbus.rs +++ b/nautilus_core/common/src/python/msgbus.rs @@ -90,7 +90,7 @@ impl MessageBus { ) { // Updates value if key already exists let handler = ShareableMessageHandler(Rc::new(handler)); - slf.subscribe(topic, handler, priority); + slf.subscribe(Ustr::from(topic), handler, priority); } /// Returns whether there are subscribers for the given `pattern`. @@ -105,7 +105,7 @@ impl MessageBus { #[pyo3(name = "unsubscribe")] pub fn unsubscribe_py(&mut self, topic: &str, handler: PythonMessageHandler) { let handler = ShareableMessageHandler(Rc::new(handler)); - self.unsubscribe(topic, handler); + self.unsubscribe(Ustr::from(topic), handler); } /// Returns whether there are subscribers for the given `pattern`. diff --git a/nautilus_core/data/src/engine/mod.rs b/nautilus_core/data/src/engine/mod.rs index 05775122168f..1d6efa068ed8 100644 --- a/nautilus_core/data/src/engine/mod.rs +++ b/nautilus_core/data/src/engine/mod.rs @@ -309,7 +309,7 @@ impl DataEngine { } } - pub fn process(&self, data: Data) { + pub fn process(&mut self, data: Data) { match data { Data::Delta(delta) => self.handle_delta(delta), Data::Deltas(deltas) => self.handle_deltas(deltas.deref().clone()), // TODO: Optimize @@ -353,7 +353,7 @@ impl DataEngine { // -- DATA HANDLERS --------------------------------------------------------------------------- // TODO: Fix all handlers to not use msgbus - fn handle_instrument(&self, instrument: InstrumentAny) { + fn handle_instrument(&mut self, instrument: InstrumentAny) { if let Err(e) = self .cache .as_ref() @@ -363,73 +363,61 @@ impl DataEngine { log::error!("Error on cache insert: {e}"); } - let topic = get_instrument_publish_topic(&instrument); - self.msgbus - .as_ref() - .borrow() - .publish(&topic, &instrument as &dyn Any); // TODO: Optimize + let mut msgbus = self.msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_instrument_topic(instrument.id()); + msgbus.publish(&topic, &instrument as &dyn Any); // TODO: Optimize } - fn handle_delta(&self, delta: OrderBookDelta) { + fn handle_delta(&mut self, delta: OrderBookDelta) { // TODO: Manage buffered deltas // TODO: Manage book - let topic = get_delta_publish_topic(&delta); - self.msgbus - .as_ref() - .borrow() - .publish(&topic, &delta as &dyn Any); // TODO: Optimize + let mut msgbus = self.msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_delta_topic(delta.instrument_id); + msgbus.publish(&topic, &delta as &dyn Any); // TODO: Optimize } - fn handle_deltas(&self, deltas: OrderBookDeltas) { + fn handle_deltas(&mut self, deltas: OrderBookDeltas) { // TODO: Manage book - let topic = get_deltas_publish_topic(&deltas); - self.msgbus - .as_ref() - .borrow() - .publish(&topic, &deltas as &dyn Any); // TODO: Optimize + let mut msgbus = self.msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_deltas_topic(deltas.instrument_id); + msgbus.publish(&topic, &deltas as &dyn Any); // TODO: Optimize } - fn handle_depth10(&self, depth: OrderBookDepth10) { + fn handle_depth10(&mut self, depth: OrderBookDepth10) { // TODO: Manage book - let topic = get_depth_publish_topic(&depth); - self.msgbus - .as_ref() - .borrow() - .publish(&topic, &depth as &dyn Any); // TODO: Optimize + let mut msgbus = self.msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_depth_topic(depth.instrument_id); + msgbus.publish(&topic, &depth as &dyn Any); // TODO: Optimize } - fn handle_quote(&self, quote: QuoteTick) { + fn handle_quote(&mut self, quote: QuoteTick) { if let Err(e) = self.cache.as_ref().borrow_mut().add_quote(quote) { log::error!("Error on cache insert: {e}"); } // TODO: Handle synthetics - let topic = get_quote_publish_topic("e); - self.msgbus - .as_ref() - .borrow() - .publish(&topic, "e as &dyn Any); // TODO: Optimize + let mut msgbus = self.msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_quote_topic(quote.instrument_id); + msgbus.publish(&topic, "e as &dyn Any); // TODO: Optimize } - fn handle_trade(&self, trade: TradeTick) { + fn handle_trade(&mut self, trade: TradeTick) { if let Err(e) = self.cache.as_ref().borrow_mut().add_trade(trade) { log::error!("Error on cache insert: {e}"); } // TODO: Handle synthetics - let topic = get_trade_publish_topic(&trade); - self.msgbus - .as_ref() - .borrow() - .publish(&topic, &trade as &dyn Any); // TODO: Optimize + let mut msgbus = self.msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_trade_topic(trade.instrument_id); + msgbus.publish(&topic, &trade as &dyn Any); // TODO: Optimize } - fn handle_bar(&self, bar: Bar) { + fn handle_bar(&mut self, bar: Bar) { // TODO: Handle additional bar logic if self.config.validate_data_sequence { if let Some(last_bar) = self.cache.as_ref().borrow().bar(&bar.bar_type) { @@ -455,11 +443,9 @@ impl DataEngine { log::error!("Error on cache insert: {e}"); } - let topic = get_bar_publish_topic(&bar); - self.msgbus - .as_ref() - .borrow() - .publish(&topic, &bar as &dyn Any); // TODO: Optimize + let mut msgbus = self.msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_bar_topic(bar.bar_type); + msgbus.publish(&topic, &bar as &dyn Any); // TODO: Optimize } // -- RESPONSE HANDLERS ----------------------------------------------------------------------- @@ -513,61 +499,6 @@ impl DataEngine { } } -// TODO: Potentially move these -#[must_use] -pub fn get_instrument_publish_topic(instrument: &InstrumentAny) -> String { - let instrument_id = instrument.id(); - format!( - "data.instrument.{}.{}", - instrument_id.venue, instrument_id.symbol - ) -} - -#[must_use] -pub fn get_delta_publish_topic(delta: &OrderBookDelta) -> String { - format!( - "data.book.delta.{}.{}", - delta.instrument_id.venue, delta.instrument_id.symbol - ) -} - -#[must_use] -pub fn get_deltas_publish_topic(delta: &OrderBookDeltas) -> String { - format!( - "data.book.snapshots.{}.{}", - delta.instrument_id.venue, delta.instrument_id.symbol - ) -} - -#[must_use] -pub fn get_depth_publish_topic(depth: &OrderBookDepth10) -> String { - format!( - "data.book.depth.{}.{}", - depth.instrument_id.venue, depth.instrument_id.symbol - ) -} - -#[must_use] -pub fn get_quote_publish_topic(quote: &QuoteTick) -> String { - format!( - "data.quotes.{}.{}", - quote.instrument_id.venue, quote.instrument_id.symbol - ) -} - -#[must_use] -pub fn get_trade_publish_topic(trade: &TradeTick) -> String { - format!( - "data.trades.{}.{}", - trade.instrument_id.venue, trade.instrument_id.symbol - ) -} - -#[must_use] -pub fn get_bar_publish_topic(bar: &Bar) -> String { - format!("data.bars.{}", bar.bar_type) -} - pub struct SubscriptionCommandHandler { id: Ustr, data_engine: Rc>, @@ -597,7 +528,11 @@ mod tests { use nautilus_common::{ clock::TestClock, messages::data::Action, - msgbus::{handler::ShareableMessageHandler, switchboard::MessagingSwitchboard}, + msgbus::{ + handler::ShareableMessageHandler, + stubs::{get_call_check_shareable_handler, CallCheckMessageHandler}, + switchboard::MessagingSwitchboard, + }, }; use nautilus_core::{nanos::UnixNanos, uuid::UUID4}; use nautilus_model::{ @@ -656,7 +591,7 @@ mod tests { cache: Rc>, msgbus: Rc>, ) -> Rc> { - let data_engine = DataEngine::new(clock, cache.clone(), msgbus.clone(), None); + let data_engine = DataEngine::new(clock, cache, msgbus, None); Rc::new(RefCell::new(data_engine)) } @@ -668,7 +603,7 @@ mod tests { ) -> ShareableMessageHandler { ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { id: switchboard.data_engine_execute, - data_engine: data_engine.clone(), + data_engine, })) } @@ -680,7 +615,7 @@ mod tests { msgbus: Rc>, clock: Box, ) -> DataClientAdapter { - let client = Box::new(MockDataClient::new(cache, msgbus.clone(), client_id, venue)); + let client = Box::new(MockDataClient::new(cache, msgbus, client_id, venue)); DataClientAdapter::new(client_id, venue, client, clock) } @@ -740,7 +675,7 @@ mod tests { let cmd = SubscriptionCommand::new( client_id, venue, - data_type.clone(), + data_type, Action::Subscribe, UUID4::new(), UnixNanos::default(), @@ -780,7 +715,7 @@ mod tests { let cmd = SubscriptionCommand::new( client_id, venue, - data_type.clone(), + data_type, Action::Subscribe, UUID4::new(), UnixNanos::default(), @@ -953,4 +888,104 @@ mod tests { assert!(data_engine.borrow().subscribed_bars().contains(&bar_type)); } + + #[rstest] + fn test_process_quote_tick( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id.to_string(), + }; + let data_type = DataType::new(stringify!(QuoteTick), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + let quote = QuoteTick::default(); + + let mut msgbus = msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_quote_topic(quote.instrument_id); + let handler = get_call_check_shareable_handler(Ustr::from("quote-handler")); + msgbus.subscribe(topic, handler.clone(), None); + + data_engine.borrow_mut().process(Data::Quote(quote)); + assert!(!handler + .0 + .as_ref() + .as_any() + .downcast_ref::() + .unwrap() + .was_called()); + } + + #[rstest] + fn test_process_trade_tick( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id.to_string(), + }; + let data_type = DataType::new(stringify!(QuoteTick), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + let trade = TradeTick::default(); + + let mut msgbus = msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_quote_topic(trade.instrument_id); + let handler = get_call_check_shareable_handler(Ustr::from("trade-handler")); + msgbus.subscribe(topic, handler.clone(), None); + + data_engine.borrow_mut().process(Data::Trade(trade)); + assert!(!handler + .0 + .as_ref() + .as_any() + .downcast_ref::() + .unwrap() + .was_called()); + } } diff --git a/nautilus_core/data/src/engine/runner.rs b/nautilus_core/data/src/engine/runner.rs index eb3b9da9b806..1d3ce7305f30 100644 --- a/nautilus_core/data/src/engine/runner.rs +++ b/nautilus_core/data/src/engine/runner.rs @@ -24,7 +24,7 @@ pub trait Runner { type Sender; fn new() -> Self; - fn run(&mut self, engine: &DataEngine); + fn run(&mut self, engine: &mut DataEngine); fn get_sender(&self) -> Self::Sender; } @@ -47,7 +47,7 @@ impl Runner for BacktestRunner { } } - fn run(&mut self, engine: &DataEngine) { + fn run(&mut self, engine: &mut DataEngine) { while let Some(resp) = self.queue.as_ref().borrow_mut().pop_front() { match resp { DataClientResponse::Response(resp) => engine.response(resp), @@ -74,7 +74,7 @@ impl Runner for LiveRunner { Self { resp_tx, resp_rx } } - fn run(&mut self, engine: &DataEngine) { + fn run(&mut self, engine: &mut DataEngine) { while let Some(resp) = self.resp_rx.blocking_recv() { match resp { DataClientResponse::Response(resp) => engine.response(resp), diff --git a/nautilus_core/model/src/data/deltas.rs b/nautilus_core/model/src/data/deltas.rs index 69e40626348a..e76d558b81fd 100644 --- a/nautilus_core/model/src/data/deltas.rs +++ b/nautilus_core/model/src/data/deltas.rs @@ -121,7 +121,7 @@ impl GetTsInit for OrderBookDeltas { /// dereferenced to `OrderBookDeltas`, providing access to `OrderBookDeltas`'s methods without /// having to manually access the underlying `OrderBookDeltas` instance. #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[allow(non_camel_case_types)] pub struct OrderBookDeltas_API(Box); diff --git a/nautilus_core/model/src/data/mod.rs b/nautilus_core/model/src/data/mod.rs index b82689516d47..b99863808b1c 100644 --- a/nautilus_core/model/src/data/mod.rs +++ b/nautilus_core/model/src/data/mod.rs @@ -50,7 +50,7 @@ use crate::{ /// Not recommended for storing large amounts of data, as the largest variant is significantly /// larger (10x) than the smallest. #[repr(C)] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum Data { Delta(OrderBookDelta), From 3fc31d647427a588d22c37f34ca6a0510739554a Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 10 Aug 2024 14:58:18 +1000 Subject: [PATCH 06/72] Improve comments --- nautilus_trader/live/execution_engine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nautilus_trader/live/execution_engine.py b/nautilus_trader/live/execution_engine.py index ecf786f1f41e..e87a01e4c0fa 100644 --- a/nautilus_trader/live/execution_engine.py +++ b/nautilus_trader/live/execution_engine.py @@ -349,9 +349,9 @@ def _on_stop(self) -> None: self._inflight_check_task = None if self._kill: - return # Avoids queuing redundant sentinel messages + return # Avoids enqueuing unnecessary sentinel messages when termination already signaled - # This will stop the queues processing as soon as they see the sentinel message + # This will stop queue processing as soon as they 'see' the sentinel message self._enqueue_sentinel() async def _wait_for_inflight_check_task(self) -> None: From 6681fe6c1e96b4a9fb2a071cbd14ae11647e30f4 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 10 Aug 2024 17:25:12 +1000 Subject: [PATCH 07/72] Improve Binance position report requests --- nautilus_trader/adapters/binance/common/execution.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/nautilus_trader/adapters/binance/common/execution.py b/nautilus_trader/adapters/binance/common/execution.py index 10a27b28cfb8..1173e326181f 100644 --- a/nautilus_trader/adapters/binance/common/execution.py +++ b/nautilus_trader/adapters/binance/common/execution.py @@ -543,11 +543,14 @@ async def generate_position_status_reports( start: pd.Timestamp | None = None, end: pd.Timestamp | None = None, ) -> list[PositionStatusReport]: - self._log.info("Requesting PositionStatusReports...") - try: - symbol = instrument_id.symbol.value if instrument_id is not None else None - reports = await self._get_binance_position_status_reports(symbol) + if instrument_id: + self._log.info(f"Requesting PositionStatusReport for {instrument_id}") + symbol = instrument_id.symbol.value + reports = await self._get_binance_position_status_reports(symbol) + else: + self._log.info("Requesting PositionStatusReports...") + reports = await self._get_binance_position_status_reports() except BinanceError as e: self._log.exception(f"Cannot generate PositionStatusReport: {e.message}", e) return [] From 7358ae9907342142e97cc99d7d465541bd25080c Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 10 Aug 2024 17:25:26 +1000 Subject: [PATCH 08/72] Improve Bybit position report requests --- nautilus_trader/adapters/bybit/execution.py | 37 ++++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/nautilus_trader/adapters/bybit/execution.py b/nautilus_trader/adapters/bybit/execution.py index 3ec49cf59489..eb54e7386cbb 100644 --- a/nautilus_trader/adapters/bybit/execution.py +++ b/nautilus_trader/adapters/bybit/execution.py @@ -401,28 +401,47 @@ async def generate_position_status_reports( start: pd.Timestamp | None = None, end: pd.Timestamp | None = None, ) -> list[PositionStatusReport]: - self._log.info("Requesting PositionStatusReports...") reports: list[PositionStatusReport] = [] try: - for product_type in self._product_types: - if product_type == BybitProductType.SPOT: - continue # No positions on spot - positions = await self._http_account.query_position_info(product_type) + if instrument_id: + self._log.info(f"Requesting PositionStatusReport for {instrument_id}") + bybit_symbol = BybitSymbol(instrument_id.symbol.value) + positions = await self._http_account.query_position_info( + bybit_symbol.product_type, + bybit_symbol.raw_symbol, + ) for position in positions: # Uncomment for development # self._log.info(f"Generating report {position}", LogColor.MAGENTA) - instr: InstrumentId = BybitSymbol( - position.symbol + "-" + product_type.value.upper(), - ).parse_as_nautilus() position_report = position.parse_to_position_status_report( account_id=self.account_id, - instrument_id=instr, + instrument_id=instrument_id, report_id=UUID4(), ts_init=self._clock.timestamp_ns(), ) self._log.debug(f"Received {position_report}") reports.append(position_report) + else: + self._log.info("Requesting PositionStatusReports...") + for product_type in self._product_types: + if product_type == BybitProductType.SPOT: + continue # No positions on spot + positions = await self._http_account.query_position_info(product_type) + for position in positions: + # Uncomment for development + # self._log.info(f"Generating report {position}", LogColor.MAGENTA) + instr: InstrumentId = BybitSymbol( + position.symbol + "-" + product_type.value.upper(), + ).parse_as_nautilus() + position_report = position.parse_to_position_status_report( + account_id=self.account_id, + instrument_id=instr, + report_id=UUID4(), + ts_init=self._clock.timestamp_ns(), + ) + self._log.debug(f"Received {position_report}") + reports.append(position_report) except BybitError as e: self._log.error(f"Failed to generate PositionReports: {e}") From 39111253cb840aaa65823b00dab1415b8ccc62d6 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 10 Aug 2024 17:32:17 +1000 Subject: [PATCH 09/72] Improve execution reconciliation procedure --- RELEASES.md | 4 +- nautilus_trader/live/execution_engine.py | 59 +++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index e0681787d805..829356cf92b3 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,7 +3,9 @@ Released on TBD (UTC). ### Enhancements -None +- Improve `BinanceExecutionClient` position report requests (can now filter by instrument) +- Improve `BybitExecutionClient` position report requests (can now filter by instrument) +- Improve `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions ### Breaking Changes None diff --git a/nautilus_trader/live/execution_engine.py b/nautilus_trader/live/execution_engine.py index e87a01e4c0fa..a933e77fc5c3 100644 --- a/nautilus_trader/live/execution_engine.py +++ b/nautilus_trader/live/execution_engine.py @@ -434,7 +434,7 @@ async def _check_inflight_orders(self) -> None: # -- RECONCILIATION ------------------------------------------------------------------------------- - async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: + async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # noqa: C901 (too complex) """ Reconcile the internal execution state with all execution clients (external state). @@ -480,8 +480,63 @@ async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: "(likely due to an adapter client error when generating reports)", ) continue + + client_id = mass_status.client_id + venue = mass_status.venue result = self._reconcile_mass_status(mass_status) - results.append(result) + + if result: + results.append(result) + self._log.info(f"Reconciliation for {client_id} succeeded", LogColor.GREEN) + continue + + self._log.warning(f"Reconciliation for {client_id} failed") + + if self.filter_position_reports: + self._log.warning( + f"Filtering position reports enabled. Skipping further reconciliation for {client_id}", + ) + continue + + # Reconcile specific positions open + positions = self._cache.positions_open(venue) + if not positions: + self._log.warning(f"No cached open positions found for {venue}") + results.append(False) + continue + + client = self._clients.get(client_id) + + report_tasks: list[asyncio.Task] = [] + for position in positions: + instrument_id = position.instrument_id + if instrument_id in mass_status.position_reports: + self._log.debug(f"Position {instrument_id} for {client_id} already reconciled") + continue # Already reconciled + self._log.info(f"{position} pending reconciliation") + report_tasks.append(client.generate_position_report(instrument_id)) + + if not report_tasks: + self._log.warning(f"No new position reports received for {venue}") + results.append(False) + continue + + self._log.info( + f"Awaiting reconciliation for {len(report_tasks)} position reports for {client_id}", + ) + + position_reports = await asyncio.gather(*report_tasks) + position_results: list[bool] = [] + for report in position_reports: + position_result = self._reconcile_position_report(report) + instrument_id = report.instrument_id + if position_result: + self._log.info(f"Reconciliation for {instrument_id} succeeded", LogColor.GREEN) + else: + self._log.warning(f"Reconciliation for {instrument_id} failed") + position_results.append(position_result) + + results.append(all(position_results)) return all(results) From 901e4afd41dd5acf86cb9f1032be0b6cd46a9a77 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 07:22:23 +1000 Subject: [PATCH 10/72] Update dependencies --- nautilus_core/Cargo.lock | 12 +-- nautilus_core/cli/Cargo.toml | 2 +- poetry.lock | 154 +++++++++++++++++------------------ 3 files changed, 84 insertions(+), 84 deletions(-) diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index 73209f96afbc..e040dd18cf79 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -833,9 +833,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.14" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c937d4061031a6d0c8da4b9a4f98a172fc2976dfb1c19213a9cf7d0d3c837e36" +checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" dependencies = [ "clap_builder", "clap_derive", @@ -843,9 +843,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.14" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85379ba512b21a328adf887e85f7742d12e96eb31f3ef077df4ffc26b506ffed" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -1020,7 +1020,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.14", + "clap 4.5.15", "criterion-plot", "is-terminal", "itertools 0.10.5", @@ -2619,7 +2619,7 @@ name = "nautilus-cli" version = "0.29.0" dependencies = [ "anyhow", - "clap 4.5.14", + "clap 4.5.15", "clap_derive", "dotenvy", "log", diff --git a/nautilus_core/cli/Cargo.toml b/nautilus_core/cli/Cargo.toml index 377d7ff6cde2..149ce529b4b5 100644 --- a/nautilus_core/cli/Cargo.toml +++ b/nautilus_core/cli/Cargo.toml @@ -18,7 +18,7 @@ nautilus-infrastructure = { path = "../infrastructure" , features = ["postgres"] anyhow = { workspace = true } log = { workspace = true } tokio = {workspace = true} -clap = { version = "4.5.14", features = ["derive", "env"] } +clap = { version = "4.5.15", features = ["derive", "env"] } clap_derive = { version = "4.5.13" } dotenvy = { version = "0.15.7" } simple_logger = "5.0.0" diff --git a/poetry.lock b/poetry.lock index 7f2264c4735e..af75c7b1d33c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,87 +13,87 @@ files = [ [[package]] name = "aiohttp" -version = "3.10.2" +version = "3.10.3" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95213b3d79c7e387144e9cb7b9d2809092d6ff2c044cb59033aedc612f38fb6d"}, - {file = "aiohttp-3.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1aa005f060aff7124cfadaa2493f00a4e28ed41b232add5869e129a2e395935a"}, - {file = "aiohttp-3.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eabe6bf4c199687592f5de4ccd383945f485779c7ffb62a9b9f1f8a3f9756df8"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e010736fc16d21125c7e2dc5c350cd43c528b85085c04bf73a77be328fe944"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99f81f9c1529fd8e03be4a7bd7df32d14b4f856e90ef6e9cbad3415dbfa9166c"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d611d1a01c25277bcdea06879afbc11472e33ce842322496b211319aa95441bb"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00191d38156e09e8c81ef3d75c0d70d4f209b8381e71622165f22ef7da6f101"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74c091a5ded6cb81785de2d7a8ab703731f26de910dbe0f3934eabef4ae417cc"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:18186a80ec5a701816adbf1d779926e1069392cf18504528d6e52e14b5920525"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5a7ceb2a0d2280f23a02c64cd0afdc922079bb950400c3dd13a1ab2988428aac"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8bd7be6ff6c162a60cb8fce65ee879a684fbb63d5466aba3fa5b9288eb04aefa"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fae962b62944eaebff4f4fddcf1a69de919e7b967136a318533d82d93c3c6bd1"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a0fde16d284efcacbe15fb0c1013f0967b6c3e379649239d783868230bf1db42"}, - {file = "aiohttp-3.10.2-cp310-cp310-win32.whl", hash = "sha256:f81cd85a0e76ec7b8e2b6636fe02952d35befda4196b8c88f3cec5b4fb512839"}, - {file = "aiohttp-3.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:54ba10eb5a3481c28282eb6afb5f709aedf53cf9c3a31875ffbdc9fc719ffd67"}, - {file = "aiohttp-3.10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87fab7f948e407444c2f57088286e00e2ed0003ceaf3d8f8cc0f60544ba61d91"}, - {file = "aiohttp-3.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6ad66ed660d46503243cbec7b2b3d8ddfa020f984209b3b8ef7d98ce69c3f2"}, - {file = "aiohttp-3.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a4be88807283bd96ae7b8e401abde4ca0bab597ba73b5e9a2d98f36d451e9aac"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01c98041f90927c2cbd72c22a164bb816fa3010a047d264969cf82e1d4bcf8d1"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54e36c67e1a9273ecafab18d6693da0fb5ac48fd48417e4548ac24a918c20998"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7de3ddb6f424af54535424082a1b5d1ae8caf8256ebd445be68c31c662354720"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dd9c7db94b4692b827ce51dcee597d61a0e4f4661162424faf65106775b40e7"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e57e21e1167705f8482ca29cc5d02702208d8bf4aff58f766d94bcd6ead838cd"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a1a50e59b720060c29e2951fd9f13c01e1ea9492e5a527b92cfe04dd64453c16"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:686c87782481fda5ee6ba572d912a5c26d9f98cc5c243ebd03f95222af3f1b0f"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:dafb4abb257c0ed56dc36f4e928a7341b34b1379bd87e5a15ce5d883c2c90574"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:494a6f77560e02bd7d1ab579fdf8192390567fc96a603f21370f6e63690b7f3d"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6fe8503b1b917508cc68bf44dae28823ac05e9f091021e0c41f806ebbb23f92f"}, - {file = "aiohttp-3.10.2-cp311-cp311-win32.whl", hash = "sha256:4ddb43d06ce786221c0dfd3c91b4892c318eaa36b903f7c4278e7e2fa0dd5102"}, - {file = "aiohttp-3.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:ca2f5abcb0a9a47e56bac173c01e9f6c6e7f27534d91451c5f22e6a35a5a2093"}, - {file = "aiohttp-3.10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:14eb6b17f6246959fb0b035d4f4ae52caa870c4edfb6170aad14c0de5bfbf478"}, - {file = "aiohttp-3.10.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:465e445ec348d4e4bd349edd8b22db75f025da9d7b6dc1369c48e7935b85581e"}, - {file = "aiohttp-3.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:341f8ece0276a828d95b70cd265d20e257f5132b46bf77d759d7f4e0443f2906"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01fbb87b5426381cd9418b3ddcf4fc107e296fa2d3446c18ce6c76642f340a3"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c474af073e1a6763e1c5522bbb2d85ff8318197e4c6c919b8d7886e16213345"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d9076810a5621236e29b2204e67a68e1fe317c8727ee4c9abbfbb1083b442c38"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8f515d6859e673940e08de3922b9c4a2249653b0ac181169313bd6e4b1978ac"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:655e583afc639bef06f3b2446972c1726007a21003cd0ef57116a123e44601bc"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8da9449a575133828cc99985536552ea2dcd690e848f9d41b48d8853a149a959"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19073d57d0feb1865d12361e2a1f5a49cb764bf81a4024a3b608ab521568093a"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8e98e1845805f184d91fda6f9ab93d7c7b0dddf1c07e0255924bfdb151a8d05"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:377220a5efde6f9497c5b74649b8c261d3cce8a84cb661be2ed8099a2196400a"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92f7f4a4dc9cdb5980973a74d43cdbb16286dacf8d1896b6c3023b8ba8436f8e"}, - {file = "aiohttp-3.10.2-cp312-cp312-win32.whl", hash = "sha256:9bb2834a6f11d65374ce97d366d6311a9155ef92c4f0cee543b2155d06dc921f"}, - {file = "aiohttp-3.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:518dc3cb37365255708283d1c1c54485bbacccd84f0a0fb87ed8917ba45eda5b"}, - {file = "aiohttp-3.10.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7f98e70bbbf693086efe4b86d381efad8edac040b8ad02821453083d15ec315f"}, - {file = "aiohttp-3.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f6f0b252a009e98fe84028a4ec48396a948e7a65b8be06ccfc6ef68cf1f614d"}, - {file = "aiohttp-3.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9360e3ffc7b23565600e729e8c639c3c50d5520e05fdf94aa2bd859eef12c407"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3988044d1635c7821dd44f0edfbe47e9875427464e59d548aece447f8c22800a"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a9d59da1543a6f1478c3436fd49ec59be3868bca561a33778b4391005e499d"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f49bdb94809ac56e09a310a62f33e5f22973d6fd351aac72a39cd551e98194"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfd2dca3f11c365d6857a07e7d12985afc59798458a2fdb2ffa4a0332a3fd43"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c1508ec97b2cd3e120bfe309a4ff8e852e8a7460f1ef1de00c2c0ed01e33c"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:49904f38667c44c041a0b44c474b3ae36948d16a0398a8f8cd84e2bb3c42a069"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:352f3a4e5f11f3241a49b6a48bc5b935fabc35d1165fa0d87f3ca99c1fcca98b"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:fc61f39b534c5d5903490478a0dd349df397d2284a939aa3cbaa2fb7a19b8397"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:ad2274e707be37420d0b6c3d26a8115295fe9d8e6e530fa6a42487a8ca3ad052"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c836bf3c7512100219fe1123743fd8dd9a2b50dd7cfb0c3bb10d041309acab4b"}, - {file = "aiohttp-3.10.2-cp38-cp38-win32.whl", hash = "sha256:53e8898adda402be03ff164b0878abe2d884e3ea03a4701e6ad55399d84b92dc"}, - {file = "aiohttp-3.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:7cc8f65f5b22304693de05a245b6736b14cb5bc9c8a03da6e2ae9ef15f8b458f"}, - {file = "aiohttp-3.10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9dfc906d656e14004c5bc672399c1cccc10db38df2b62a13fb2b6e165a81c316"}, - {file = "aiohttp-3.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:91b10208b222ddf655c3a3d5b727879d7163db12b634492df41a9182a76edaae"}, - {file = "aiohttp-3.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9fd16b5e1a7bdd14668cd6bde60a2a29b49147a535c74f50d8177d11b38433a7"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2bfdda4971bd79201f59adbad24ec2728875237e1c83bba5221284dbbf57bda"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69d73f869cf29e8a373127fc378014e2b17bcfbe8d89134bc6fb06a2f67f3cb3"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df59f8486507c421c0620a2c3dce81fbf1d54018dc20ff4fecdb2c106d6e6abc"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df930015db36b460aa9badbf35eccbc383f00d52d4b6f3de2ccb57d064a6ade"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:562b1153ab7f766ee6b8b357ec777a302770ad017cf18505d34f1c088fccc448"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d984db6d855de58e0fde1ef908d48fe9a634cadb3cf715962722b4da1c40619d"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:14dc3fcb0d877911d775d511eb617a486a8c48afca0a887276e63db04d3ee920"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b52a27a5c97275e254704e1049f4b96a81e67d6205f52fa37a4777d55b0e98ef"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:cd33d9de8cfd006a0d0fe85f49b4183c57e91d18ffb7e9004ce855e81928f704"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1238fc979160bc03a92fff9ad021375ff1c8799c6aacb0d8ea1b357ea40932bb"}, - {file = "aiohttp-3.10.2-cp39-cp39-win32.whl", hash = "sha256:e2f43d238eae4f0b04f58d4c0df4615697d4ca3e9f9b1963d49555a94f0f5a04"}, - {file = "aiohttp-3.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:947847f07a8f81d7b39b2d0202fd73e61962ebe17ac2d8566f260679e467da7b"}, - {file = "aiohttp-3.10.2.tar.gz", hash = "sha256:4d1f694b5d6e459352e5e925a42e05bac66655bfde44d81c59992463d2897014"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc36cbdedf6f259371dbbbcaae5bb0e95b879bc501668ab6306af867577eb5db"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85466b5a695c2a7db13eb2c200af552d13e6a9313d7fa92e4ffe04a2c0ea74c1"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71bb1d97bfe7e6726267cea169fdf5df7658831bb68ec02c9c6b9f3511e108bb"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baec1eb274f78b2de54471fc4c69ecbea4275965eab4b556ef7a7698dee18bf2"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13031e7ec1188274bad243255c328cc3019e36a5a907978501256000d57a7201"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bbc55a964b8eecb341e492ae91c3bd0848324d313e1e71a27e3d96e6ee7e8e8"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8cc0564b286b625e673a2615ede60a1704d0cbbf1b24604e28c31ed37dc62aa"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f817a54059a4cfbc385a7f51696359c642088710e731e8df80d0607193ed2b73"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8542c9e5bcb2bd3115acdf5adc41cda394e7360916197805e7e32b93d821ef93"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:671efce3a4a0281060edf9a07a2f7e6230dca3a1cbc61d110eee7753d28405f7"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0974f3b5b0132edcec92c3306f858ad4356a63d26b18021d859c9927616ebf27"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:44bb159b55926b57812dca1b21c34528e800963ffe130d08b049b2d6b994ada7"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6ae9ae382d1c9617a91647575255ad55a48bfdde34cc2185dd558ce476bf16e9"}, + {file = "aiohttp-3.10.3-cp310-cp310-win32.whl", hash = "sha256:aed12a54d4e1ee647376fa541e1b7621505001f9f939debf51397b9329fd88b9"}, + {file = "aiohttp-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b51aef59370baf7444de1572f7830f59ddbabd04e5292fa4218d02f085f8d299"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e021c4c778644e8cdc09487d65564265e6b149896a17d7c0f52e9a088cc44e1b"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:24fade6dae446b183e2410a8628b80df9b7a42205c6bfc2eff783cbeedc224a2"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc8e9f15939dacb0e1f2d15f9c41b786051c10472c7a926f5771e99b49a5957f"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5a9ec959b5381271c8ec9310aae1713b2aec29efa32e232e5ef7dcca0df0279"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a5d0ea8a6467b15d53b00c4e8ea8811e47c3cc1bdbc62b1aceb3076403d551f"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9ed607dbbdd0d4d39b597e5bf6b0d40d844dfb0ac6a123ed79042ef08c1f87e"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e66d5b506832e56add66af88c288c1d5ba0c38b535a1a59e436b300b57b23e"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fda91ad797e4914cca0afa8b6cccd5d2b3569ccc88731be202f6adce39503189"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:61ccb867b2f2f53df6598eb2a93329b5eee0b00646ee79ea67d68844747a418e"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d881353264e6156f215b3cb778c9ac3184f5465c2ece5e6fce82e68946868ef"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b031ce229114825f49cec4434fa844ccb5225e266c3e146cb4bdd025a6da52f1"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5337cc742a03f9e3213b097abff8781f79de7190bbfaa987bd2b7ceb5bb0bdec"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab3361159fd3dcd0e48bbe804006d5cfb074b382666e6c064112056eb234f1a9"}, + {file = "aiohttp-3.10.3-cp311-cp311-win32.whl", hash = "sha256:05d66203a530209cbe40f102ebaac0b2214aba2a33c075d0bf825987c36f1f0b"}, + {file = "aiohttp-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:70b4a4984a70a2322b70e088d654528129783ac1ebbf7dd76627b3bd22db2f17"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:166de65e2e4e63357cfa8417cf952a519ac42f1654cb2d43ed76899e2319b1ee"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7084876352ba3833d5d214e02b32d794e3fd9cf21fdba99cff5acabeb90d9806"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d98c604c93403288591d7d6d7d6cc8a63459168f8846aeffd5b3a7f3b3e5e09"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d73b073a25a0bb8bf014345374fe2d0f63681ab5da4c22f9d2025ca3e3ea54fc"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8da6b48c20ce78f5721068f383e0e113dde034e868f1b2f5ee7cb1e95f91db57"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a9dcdccf50284b1b0dc72bc57e5bbd3cc9bf019060dfa0668f63241ccc16aa7"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56fb94bae2be58f68d000d046172d8b8e6b1b571eb02ceee5535e9633dcd559c"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf75716377aad2c718cdf66451c5cf02042085d84522aec1f9246d3e4b8641a6"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c51ed03e19c885c8e91f574e4bbe7381793f56f93229731597e4a499ffef2a5"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b84857b66fa6510a163bb083c1199d1ee091a40163cfcbbd0642495fed096204"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c124b9206b1befe0491f48185fd30a0dd51b0f4e0e7e43ac1236066215aff272"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3461d9294941937f07bbbaa6227ba799bc71cc3b22c40222568dc1cca5118f68"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08bd0754d257b2db27d6bab208c74601df6f21bfe4cb2ec7b258ba691aac64b3"}, + {file = "aiohttp-3.10.3-cp312-cp312-win32.whl", hash = "sha256:7f9159ae530297f61a00116771e57516f89a3de6ba33f314402e41560872b50a"}, + {file = "aiohttp-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:e1128c5d3a466279cb23c4aa32a0f6cb0e7d2961e74e9e421f90e74f75ec1edf"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d1100e68e70eb72eadba2b932b185ebf0f28fd2f0dbfe576cfa9d9894ef49752"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a541414578ff47c0a9b0b8b77381ea86b0c8531ab37fc587572cb662ccd80b88"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d5548444ef60bf4c7b19ace21f032fa42d822e516a6940d36579f7bfa8513f9c"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba2e838b5e6a8755ac8297275c9460e729dc1522b6454aee1766c6de6d56e5e"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48665433bb59144aaf502c324694bec25867eb6630fcd831f7a893ca473fcde4"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bac352fceed158620ce2d701ad39d4c1c76d114255a7c530e057e2b9f55bdf9f"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0f670502100cdc567188c49415bebba947eb3edaa2028e1a50dd81bd13363f"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b09f38a67679e32d380fe512189ccb0b25e15afc79b23fbd5b5e48e4fc8fd9"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:cd788602e239ace64f257d1c9d39898ca65525583f0fbf0988bcba19418fe93f"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:214277dcb07ab3875f17ee1c777d446dcce75bea85846849cc9d139ab8f5081f"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:32007fdcaab789689c2ecaaf4b71f8e37bf012a15cd02c0a9db8c4d0e7989fa8"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:123e5819bfe1b87204575515cf448ab3bf1489cdeb3b61012bde716cda5853e7"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:812121a201f0c02491a5db335a737b4113151926a79ae9ed1a9f41ea225c0e3f"}, + {file = "aiohttp-3.10.3-cp38-cp38-win32.whl", hash = "sha256:b97dc9a17a59f350c0caa453a3cb35671a2ffa3a29a6ef3568b523b9113d84e5"}, + {file = "aiohttp-3.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:3731a73ddc26969d65f90471c635abd4e1546a25299b687e654ea6d2fc052394"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38d91b98b4320ffe66efa56cb0f614a05af53b675ce1b8607cdb2ac826a8d58e"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9743fa34a10a36ddd448bba8a3adc2a66a1c575c3c2940301bacd6cc896c6bf1"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7c126f532caf238031c19d169cfae3c6a59129452c990a6e84d6e7b198a001dc"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926e68438f05703e500b06fe7148ef3013dd6f276de65c68558fa9974eeb59ad"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:434b3ab75833accd0b931d11874e206e816f6e6626fd69f643d6a8269cd9166a"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d35235a44ec38109b811c3600d15d8383297a8fab8e3dec6147477ec8636712a"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59c489661edbd863edb30a8bd69ecb044bd381d1818022bc698ba1b6f80e5dd1"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50544fe498c81cb98912afabfc4e4d9d85e89f86238348e3712f7ca6a2f01dab"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09bc79275737d4dc066e0ae2951866bb36d9c6b460cb7564f111cc0427f14844"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:af4dbec58e37f5afff4f91cdf235e8e4b0bd0127a2a4fd1040e2cad3369d2f06"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b22cae3c9dd55a6b4c48c63081d31c00fc11fa9db1a20c8a50ee38c1a29539d2"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ba562736d3fbfe9241dad46c1a8994478d4a0e50796d80e29d50cabe8fbfcc3f"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f25d6c4e82d7489be84f2b1c8212fafc021b3731abdb61a563c90e37cced3a21"}, + {file = "aiohttp-3.10.3-cp39-cp39-win32.whl", hash = "sha256:b69d832e5f5fa15b1b6b2c8eb6a9fd2c0ec1fd7729cb4322ed27771afc9fc2ac"}, + {file = "aiohttp-3.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:673bb6e3249dc8825df1105f6ef74e2eab779b7ff78e96c15cadb78b04a83752"}, + {file = "aiohttp-3.10.3.tar.gz", hash = "sha256:21650e7032cc2d31fc23d353d7123e771354f2a3d5b05a5647fc30fea214e696"}, ] [package.dependencies] From dbf19a7b16a1b6f25df75b9b9d9241bc53d109b7 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 09:35:39 +1000 Subject: [PATCH 11/72] Refine MessageBus and stub handlers in Rust --- nautilus_core/Cargo.lock | 1 + nautilus_core/backtest/src/matching_engine.rs | 12 +- nautilus_core/common/Cargo.toml | 1 + nautilus_core/common/src/cache/mod.rs | 4 + nautilus_core/common/src/msgbus/mod.rs | 90 +++++++-------- nautilus_core/common/src/msgbus/stubs.rs | 47 +++++++- .../common/src/msgbus/switchboard.rs | 3 +- nautilus_core/data/src/engine/mod.rs | 105 +++++++++++------- 8 files changed, 159 insertions(+), 104 deletions(-) diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index e040dd18cf79..a4cf1571f451 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -2658,6 +2658,7 @@ dependencies = [ "tracing", "tracing-subscriber", "ustr", + "uuid", ] [[package]] diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index 52e942b2d864..efedf0d4f48b 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -822,7 +822,7 @@ mod tests { cache::Cache, msgbus::{ handler::ShareableMessageHandler, - stubs::{get_message_saving_handler, MessageSavingHandler}, + stubs::{get_message_saving_handler, get_saved_messages}, MessageBus, }, }; @@ -870,7 +870,7 @@ mod tests { #[fixture] fn order_event_handler() -> ShareableMessageHandler { - get_message_saving_handler::(Ustr::from("ExecEngine.process")) + get_message_saving_handler::(Some(Ustr::from("ExecEngine.process"))) } // for valid es futures contract currently active @@ -918,13 +918,7 @@ mod tests { fn get_order_event_handler_messages( event_handler: ShareableMessageHandler, ) -> Vec { - event_handler - .0 - .as_ref() - .as_any() - .downcast_ref::>() - .unwrap() - .get_messages() + get_saved_messages::(event_handler) } // -- TESTS ----------------------------------------------------------------------------------- diff --git a/nautilus_core/common/Cargo.toml b/nautilus_core/common/Cargo.toml index 453efc32ac27..31cb3a84bfb1 100644 --- a/nautilus_core/common/Cargo.toml +++ b/nautilus_core/common/Cargo.toml @@ -33,6 +33,7 @@ tokio = { workspace = true } tracing-subscriber = { version = "0.3.18", default-features = false, features = ["smallvec", "fmt", "ansi", "std", "env-filter"] } tracing = { workspace = true } ustr = { workspace = true } +uuid = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/nautilus_core/common/src/cache/mod.rs b/nautilus_core/common/src/cache/mod.rs index 20fe478fbb4b..91e728171413 100644 --- a/nautilus_core/common/src/cache/mod.rs +++ b/nautilus_core/common/src/cache/mod.rs @@ -221,6 +221,10 @@ pub struct Cache { position_snapshots: HashMap, } +// SAFETY: Cache is not meant to be passed between threads +unsafe impl Send for Cache {} +unsafe impl Sync for Cache {} + impl Default for Cache { /// Creates a new default [`Cache`] instance. fn default() -> Self { diff --git a/nautilus_core/common/src/msgbus/mod.rs b/nautilus_core/common/src/msgbus/mod.rs index 3fcb39054964..e7cd3b40beff 100644 --- a/nautilus_core/common/src/msgbus/mod.rs +++ b/nautilus_core/common/src/msgbus/mod.rs @@ -162,8 +162,9 @@ pub struct MessageBus { endpoints: IndexMap, } -/// Message bus is not meant to be passed between threads +// SAFETY: Message bus is not meant to be passed between threads unsafe impl Send for MessageBus {} +unsafe impl Sync for MessageBus {} impl MessageBus { /// Creates a new [`MessageBus`] instance. @@ -245,12 +246,21 @@ impl MessageBus { /// Registers the given `handler` for the `endpoint` address. pub fn register(&mut self, endpoint: Ustr, handler: ShareableMessageHandler) { + log::debug!( + "Registering endpoint '{endpoint}' with handler ID {} at {}", + handler.0.id(), + self.memory_address(), + ); // Updates value if key already exists self.endpoints.insert(endpoint, handler); } /// Deregisters the given `handler` for the `endpoint` address. pub fn deregister(&mut self, endpoint: &Ustr) { + log::debug!( + "Deregistering endpoint '{endpoint}' at {}", + self.memory_address() + ); // Removes entry if it exists for endpoint self.endpoints.shift_remove(endpoint); } @@ -262,6 +272,10 @@ impl MessageBus { handler: ShareableMessageHandler, priority: Option, ) { + log::debug!( + "Subscribing for topic '{topic}' at {}", + self.memory_address() + ); let sub = Subscription::new(topic, handler, priority); if self.subscriptions.contains_key(&sub) { @@ -287,6 +301,10 @@ impl MessageBus { /// Unsubscribes the given `handler` from the `topic`. pub fn unsubscribe(&mut self, topic: Ustr, handler: ShareableMessageHandler) { + log::debug!( + "Unsubscribing for topic '{topic}' at {}", + self.memory_address(), + ); let sub = Subscription::new(topic, handler, None); self.subscriptions.shift_remove(&sub); } @@ -349,12 +367,23 @@ impl MessageBus { /// Publish a message to a topic. pub fn publish(&self, topic: &Ustr, message: &dyn Any) { + log::trace!( + "Publishing topic '{topic}' {message:?} {:?}", + self as *const _ + ); let matching_subs = self.matching_subscriptions(topic); + log::trace!("Matched {} subscriptions", matching_subs.len()); + for sub in matching_subs { + log::trace!("Matched {sub:?}"); sub.handler.0.handle(message); } } + + fn memory_address(&self) -> String { + format!("{:?}", self as *const _) + } } /// Data specific functions. @@ -438,11 +467,10 @@ mod tests { use nautilus_core::uuid::UUID4; use rstest::*; + use stubs::check_handler_was_called; use super::*; - use crate::msgbus::stubs::{ - get_call_check_shareable_handler, get_stub_shareable_handler, CallCheckMessageHandler, - }; + use crate::msgbus::stubs::{get_call_check_shareable_handler, get_stub_shareable_handler}; fn stub_msgbus() -> MessageBus { MessageBus::new(TraderId::from("trader-001"), UUID4::new(), None, None) @@ -475,9 +503,7 @@ mod tests { #[rstest] fn test_is_subscribed_when_no_subscriptions() { let msgbus = stub_msgbus(); - - let handler_id = Ustr::from("1"); - let handler = get_stub_shareable_handler(handler_id); + let handler = get_stub_shareable_handler(None); assert!(!msgbus.is_subscribed("my-topic", handler)); } @@ -493,13 +519,11 @@ mod tests { fn test_regsiter_endpoint() { let mut msgbus = stub_msgbus(); let endpoint = Ustr::from("MyEndpoint"); - - let handler_id = Ustr::from("1"); - let handler = get_stub_shareable_handler(handler_id); + let handler = get_stub_shareable_handler(None); msgbus.register(endpoint, handler); - assert_eq!(msgbus.endpoints(), vec!["MyEndpoint".to_string()]); + assert_eq!(msgbus.endpoints(), vec![endpoint.to_string()]); assert!(msgbus.get_endpoint(&endpoint).is_some()); } @@ -507,42 +531,22 @@ mod tests { fn test_endpoint_send() { let mut msgbus = stub_msgbus(); let endpoint = Ustr::from("MyEndpoint"); - - let handler_id = Ustr::from("1"); - let handler = get_call_check_shareable_handler(handler_id); + let handler = get_call_check_shareable_handler(None); msgbus.register(endpoint, handler.clone()); assert!(msgbus.get_endpoint(&endpoint).is_some()); - - // check if the handler called variable is false - assert!(!handler - .0 - .as_ref() - .as_any() - .downcast_ref::() - .unwrap() - .was_called()); + assert!(!check_handler_was_called(handler.clone())); // Send a message to the endpoint msgbus.send(&endpoint, &"Test Message"); - - // Check if the handler was called - assert!(handler - .0 - .as_ref() - .as_any() - .downcast_ref::() - .unwrap() - .was_called()); + assert!(check_handler_was_called(handler)); } #[rstest] fn test_deregsiter_endpoint() { let mut msgbus = stub_msgbus(); let endpoint = Ustr::from("MyEndpoint"); - - let handler_id = Ustr::from("1"); - let handler = get_stub_shareable_handler(handler_id); + let handler = get_stub_shareable_handler(None); msgbus.register(endpoint, handler); msgbus.deregister(&endpoint); @@ -554,9 +558,7 @@ mod tests { fn test_subscribe() { let mut msgbus = stub_msgbus(); let topic = Ustr::from("my-topic"); - - let handler_id = Ustr::from("1"); - let handler = get_stub_shareable_handler(handler_id); + let handler = get_stub_shareable_handler(None); msgbus.subscribe(topic, handler, Some(1)); @@ -568,9 +570,7 @@ mod tests { fn test_unsubscribe() { let mut msgbus = stub_msgbus(); let topic = Ustr::from("my-topic"); - - let handler_id = Ustr::from("1"); - let handler = get_stub_shareable_handler(handler_id); + let handler = get_stub_shareable_handler(None); msgbus.subscribe(topic, handler.clone(), None); msgbus.unsubscribe(topic, handler); @@ -585,16 +585,16 @@ mod tests { let topic = Ustr::from("my-topic"); let handler_id1 = Ustr::from("1"); - let handler1 = get_stub_shareable_handler(handler_id1); + let handler1 = get_stub_shareable_handler(Some(handler_id1)); let handler_id2 = Ustr::from("2"); - let handler2 = get_stub_shareable_handler(handler_id2); + let handler2 = get_stub_shareable_handler(Some(handler_id2)); let handler_id3 = Ustr::from("3"); - let handler3 = get_stub_shareable_handler(handler_id3); + let handler3 = get_stub_shareable_handler(Some(handler_id3)); let handler_id4 = Ustr::from("4"); - let handler4 = get_stub_shareable_handler(handler_id4); + let handler4 = get_stub_shareable_handler(Some(handler_id4)); msgbus.subscribe(topic, handler1, None); msgbus.subscribe(topic, handler2, None); diff --git a/nautilus_core/common/src/msgbus/stubs.rs b/nautilus_core/common/src/msgbus/stubs.rs index fee86d36a0dd..c7545f65d5e0 100644 --- a/nautilus_core/common/src/msgbus/stubs.rs +++ b/nautilus_core/common/src/msgbus/stubs.rs @@ -26,6 +26,7 @@ use std::{ use nautilus_core::message::Message; use nautilus_model::data::Data; use ustr::Ustr; +use uuid::Uuid; use crate::{ messages::data::DataResponse, @@ -58,9 +59,13 @@ impl MessageHandler for StubMessageHandler { #[must_use] #[allow(unused_must_use)] // TODO: Temporary to fix docs build -pub fn get_stub_shareable_handler(id: Ustr) -> ShareableMessageHandler { +pub fn get_stub_shareable_handler(id: Option) -> ShareableMessageHandler { + // TODO: This reduces the need to come up with ID strings in tests. + // In Python we do something like `hash((self.topic, str(self.handler)))` for the hash + // which includes the memory address, just went with a UUID4 here. + let unique_id = id.unwrap_or_else(|| Ustr::from(&Uuid::new_v4().to_string())); ShareableMessageHandler(Rc::new(StubMessageHandler { - id, + id: unique_id, callback: Arc::new(|m: Message| { format!("{m:?}"); }), @@ -99,13 +104,28 @@ impl MessageHandler for CallCheckMessageHandler { } #[must_use] -pub fn get_call_check_shareable_handler(id: Ustr) -> ShareableMessageHandler { +pub fn get_call_check_shareable_handler(id: Option) -> ShareableMessageHandler { + // TODO: This reduces the need to come up with ID strings in tests. + // In Python we do something like `hash((self.topic, str(self.handler)))` for the hash + // which includes the memory address, just went with a UUID4 here. + let unique_id = id.unwrap_or_else(|| Ustr::from(&Uuid::new_v4().to_string())); ShareableMessageHandler(Rc::new(CallCheckMessageHandler { - id, + id: unique_id, called: Arc::new(AtomicBool::new(false)), })) } +#[must_use] +pub fn check_handler_was_called(call_check_handler: ShareableMessageHandler) -> bool { + call_check_handler + .0 + .as_ref() + .as_any() + .downcast_ref::() + .unwrap() + .was_called() +} + // Handler which saves the messages it receives #[derive(Debug, Clone)] pub struct MessageSavingHandler { @@ -143,9 +163,24 @@ impl MessageHandler for MessageSavingHandler { } #[must_use] -pub fn get_message_saving_handler(id: Ustr) -> ShareableMessageHandler { +pub fn get_message_saving_handler(id: Option) -> ShareableMessageHandler { + // TODO: This reduces the need to come up with ID strings in tests. + // In Python we do something like `hash((self.topic, str(self.handler)))` for the hash + // which includes the memory address, just went with a UUID4 here. + let unique_id = id.unwrap_or_else(|| Ustr::from(&Uuid::new_v4().to_string())); ShareableMessageHandler(Rc::new(MessageSavingHandler:: { - id, + id: unique_id, messages: Rc::new(RefCell::new(Vec::new())), })) } + +#[must_use] +pub fn get_saved_messages(handler: ShareableMessageHandler) -> Vec { + handler + .0 + .as_ref() + .as_any() + .downcast_ref::>() + .unwrap() + .get_messages() +} diff --git a/nautilus_core/common/src/msgbus/switchboard.rs b/nautilus_core/common/src/msgbus/switchboard.rs index 16bc19e3e943..646272092868 100644 --- a/nautilus_core/common/src/msgbus/switchboard.rs +++ b/nautilus_core/common/src/msgbus/switchboard.rs @@ -143,13 +143,14 @@ impl MessagingSwitchboard { //////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use super::*; use nautilus_model::{ data::{bar::BarType, DataType}, identifiers::InstrumentId, }; use rstest::*; + use super::*; + #[fixture] fn switchboard() -> MessagingSwitchboard { MessagingSwitchboard::default() diff --git a/nautilus_core/data/src/engine/mod.rs b/nautilus_core/data/src/engine/mod.rs index 1d6efa068ed8..b68157c38a9f 100644 --- a/nautilus_core/data/src/engine/mod.rs +++ b/nautilus_core/data/src/engine/mod.rs @@ -524,13 +524,17 @@ impl MessageHandler for SubscriptionCommandHandler { //////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { + use std::cell::OnceCell; + use indexmap::indexmap; + use log::LevelFilter; use nautilus_common::{ clock::TestClock, + logging::{init_logging, logger::LoggerConfig, writer::FileWriterConfig}, messages::data::Action, msgbus::{ handler::ShareableMessageHandler, - stubs::{get_call_check_shareable_handler, CallCheckMessageHandler}, + stubs::{get_message_saving_handler, get_saved_messages}, switchboard::MessagingSwitchboard, }, }; @@ -545,6 +549,17 @@ mod tests { use super::*; use crate::mocks::MockDataClient; + fn init_logger(stdout_level: LevelFilter) { + let mut config = LoggerConfig::default(); + config.stdout_level = stdout_level; + init_logging( + TraderId::default(), + UUID4::new(), + config, + FileWriterConfig::default(), + ); + } + #[fixture] fn trader_id() -> TraderId { TraderId::default() @@ -567,17 +582,31 @@ mod tests { #[fixture] fn cache() -> Rc> { + // Ensure there is only ever one instance of the cache *per test* + thread_local! { + static CACHE: OnceCell>> = OnceCell::new(); + } Rc::new(RefCell::new(Cache::default())) } #[fixture] fn msgbus(trader_id: TraderId) -> Rc> { - Rc::new(RefCell::new(MessageBus::new( - trader_id, - UUID4::new(), - None, - None, - ))) + // Ensure there is only ever one instance of the message bus *per test* + thread_local! { + static MSGBUS: OnceCell>> = OnceCell::new(); + } + + MSGBUS.with(|cell| { + cell.get_or_init(|| { + Rc::new(RefCell::new(MessageBus::new( + trader_id, + UUID4::new(), + None, + None, + ))) + }) + .clone() + }) } #[fixture] @@ -595,18 +624,6 @@ mod tests { Rc::new(RefCell::new(data_engine)) } - // TODO: For some reason using this fixture causes the tests to fail? - #[fixture] - fn subscription_handler( - data_engine: Rc>, - switchboard: MessagingSwitchboard, - ) -> ShareableMessageHandler { - ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { - id: switchboard.data_engine_execute, - data_engine, - })) - } - #[fixture] fn data_client( client_id: ClientId, @@ -924,19 +941,20 @@ mod tests { let quote = QuoteTick::default(); - let mut msgbus = msgbus.borrow_mut(); - let topic = msgbus.switchboard.get_quote_topic(quote.instrument_id); - let handler = get_call_check_shareable_handler(Ustr::from("quote-handler")); - msgbus.subscribe(topic, handler.clone(), None); + let handler = get_message_saving_handler::(None); + { + let mut msgbus = msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_quote_topic(quote.instrument_id); + msgbus.subscribe(topic, handler.clone(), None); + } - data_engine.borrow_mut().process(Data::Quote(quote)); - assert!(!handler - .0 - .as_ref() - .as_any() - .downcast_ref::() - .unwrap() - .was_called()); + let mut data_engine = data_engine.borrow_mut(); + data_engine.process(Data::Quote(quote)); + let cache = &data_engine.cache.borrow(); + let messages = get_saved_messages::(handler); + + assert_eq!(cache.quote_tick("e.instrument_id), Some(quote).as_ref()); + assert_eq!(messages.len(), 1); } #[rstest] @@ -974,18 +992,19 @@ mod tests { let trade = TradeTick::default(); - let mut msgbus = msgbus.borrow_mut(); - let topic = msgbus.switchboard.get_quote_topic(trade.instrument_id); - let handler = get_call_check_shareable_handler(Ustr::from("trade-handler")); - msgbus.subscribe(topic, handler.clone(), None); + let handler = get_message_saving_handler::(None); + { + let mut msgbus = msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_trade_topic(trade.instrument_id); + msgbus.subscribe(topic, handler.clone(), None); + } - data_engine.borrow_mut().process(Data::Trade(trade)); - assert!(!handler - .0 - .as_ref() - .as_any() - .downcast_ref::() - .unwrap() - .was_called()); + let mut data_engine = data_engine.borrow_mut(); + data_engine.process(Data::Trade(trade)); + let cache = &data_engine.cache.borrow(); + let messages = get_saved_messages::(handler); + + assert_eq!(cache.trade_tick(&trade.instrument_id), Some(trade).as_ref()); + assert_eq!(messages.len(), 1); } } From 5bf424af39272516c60dc506e6f03a64064a64d8 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 09:55:57 +1000 Subject: [PATCH 12/72] Add DataEngine tests in Rust --- nautilus_core/data/src/engine/mod.rs | 215 ++++++++++++++++++++++++++- 1 file changed, 212 insertions(+), 3 deletions(-) diff --git a/nautilus_core/data/src/engine/mod.rs b/nautilus_core/data/src/engine/mod.rs index b68157c38a9f..649e83d8b23c 100644 --- a/nautilus_core/data/src/engine/mod.rs +++ b/nautilus_core/data/src/engine/mod.rs @@ -540,6 +540,10 @@ mod tests { }; use nautilus_core::{nanos::UnixNanos, uuid::UUID4}; use nautilus_model::{ + data::{ + deltas::OrderBookDeltas_API, + stubs::{stub_delta, stub_deltas, stub_depth10}, + }, enums::BookType, identifiers::TraderId, instruments::{currency_pair::CurrencyPair, stubs::audusd_sim}, @@ -906,6 +910,160 @@ mod tests { assert!(data_engine.borrow().subscribed_bars().contains(&bar_type)); } + #[rstest] + fn test_process_order_book_delta( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id.to_string(), + "book_type".to_string() => BookType::L3_MBO.to_string(), + }; + let data_type = DataType::new(stringify!(OrderBookDelta), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + let delta = stub_delta(); + let handler = get_message_saving_handler::(None); + { + let mut msgbus = msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_delta_topic(delta.instrument_id); + msgbus.subscribe(topic, handler.clone(), None); + } + + let mut data_engine = data_engine.borrow_mut(); + data_engine.process(Data::Delta(delta)); + let cache = &data_engine.cache.borrow(); + let messages = get_saved_messages::(handler); + + assert_eq!(messages.len(), 1); + assert!(messages.contains(&delta)); + } + + #[rstest] + fn test_process_order_book_deltas( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id.to_string(), + "book_type".to_string() => BookType::L3_MBO.to_string(), + }; + let data_type = DataType::new(stringify!(OrderBookDeltas), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + // TODO: Using FFI API wrapper temporarily until Cython gone + let deltas = OrderBookDeltas_API::new(stub_deltas()); + let handler = get_message_saving_handler::(None); + { + let mut msgbus = msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_deltas_topic(deltas.instrument_id); + msgbus.subscribe(topic, handler.clone(), None); + } + + let mut data_engine = data_engine.borrow_mut(); + data_engine.process(Data::Deltas(deltas.clone())); + let cache = &data_engine.cache.borrow(); + let messages = get_saved_messages::(handler); + + assert_eq!(messages.len(), 1); + assert!(messages.contains(&deltas)); + } + + #[rstest] + fn test_process_order_book_depth10( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id.to_string(), + "book_type".to_string() => BookType::L3_MBO.to_string(), + }; + let data_type = DataType::new(stringify!(OrderBookDepth10), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + let depth = stub_depth10(); + let handler = get_message_saving_handler::(None); + { + let mut msgbus = msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_depth_topic(depth.instrument_id); + msgbus.subscribe(topic, handler.clone(), None); + } + + let mut data_engine = data_engine.borrow_mut(); + data_engine.process(Data::Depth10(depth)); + let cache = &data_engine.cache.borrow(); + let messages = get_saved_messages::(handler); + + assert_eq!(messages.len(), 1); + assert!(messages.contains(&depth)); + } + #[rstest] fn test_process_quote_tick( audusd_sim: CurrencyPair, @@ -940,7 +1098,6 @@ mod tests { msgbus.borrow().send(&endpoint, &cmd as &dyn Any); let quote = QuoteTick::default(); - let handler = get_message_saving_handler::(None); { let mut msgbus = msgbus.borrow_mut(); @@ -955,6 +1112,7 @@ mod tests { assert_eq!(cache.quote_tick("e.instrument_id), Some(quote).as_ref()); assert_eq!(messages.len(), 1); + assert!(messages.contains("e)); } #[rstest] @@ -972,7 +1130,7 @@ mod tests { let metadata = indexmap! { "instrument_id".to_string() => audusd_sim.id.to_string(), }; - let data_type = DataType::new(stringify!(QuoteTick), Some(metadata)); + let data_type = DataType::new(stringify!(TradeTick), Some(metadata)); let cmd = SubscriptionCommand::new( client_id, venue, @@ -991,7 +1149,6 @@ mod tests { msgbus.borrow().send(&endpoint, &cmd as &dyn Any); let trade = TradeTick::default(); - let handler = get_message_saving_handler::(None); { let mut msgbus = msgbus.borrow_mut(); @@ -1006,5 +1163,57 @@ mod tests { assert_eq!(cache.trade_tick(&trade.instrument_id), Some(trade).as_ref()); assert_eq!(messages.len(), 1); + assert!(messages.contains(&trade)); + } + + #[rstest] + fn test_process_bar( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let bar = Bar::default(); + let metadata = indexmap! { + "bar_type".to_string() => bar.bar_type.to_string(), + }; + let data_type = DataType::new(stringify!(Bar), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + let handler = get_message_saving_handler::(None); + { + let mut msgbus = msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_bar_topic(bar.bar_type); + msgbus.subscribe(topic, handler.clone(), None); + } + + let mut data_engine = data_engine.borrow_mut(); + data_engine.process(Data::Bar(bar)); + let cache = &data_engine.cache.borrow(); + let messages = get_saved_messages::(handler); + + assert_eq!(cache.bar(&bar.bar_type), Some(bar).as_ref()); + assert_eq!(messages.len(), 1); + assert!(messages.contains(&bar)); } } From 5b5d790e3a7717a97fa3597e3c1bc113df8c2dc8 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 11:22:49 +1000 Subject: [PATCH 13/72] Refine Cache and MessageBus --- nautilus_core/common/src/cache/mod.rs | 6 ++++++ nautilus_core/common/src/messages/data.rs | 4 +++- nautilus_core/common/src/msgbus/mod.rs | 14 ++++++++------ nautilus_core/common/src/msgbus/stubs.rs | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/nautilus_core/common/src/cache/mod.rs b/nautilus_core/common/src/cache/mod.rs index 91e728171413..381b71b54181 100644 --- a/nautilus_core/common/src/cache/mod.rs +++ b/nautilus_core/common/src/cache/mod.rs @@ -290,6 +290,12 @@ impl Cache { } } + /// Returns the cache instances memory address. + #[must_use] + pub fn memory_address(&self) -> String { + format!("{:?}", std::ptr::from_ref(self)) + } + // -- COMMANDS -------------------------------------------------------------------------------- /// Clears the current general cache and loads the general objects from the cache database. diff --git a/nautilus_core/common/src/messages/data.rs b/nautilus_core/common/src/messages/data.rs index fbfb991fd0c8..522bd5d26fd2 100644 --- a/nautilus_core/common/src/messages/data.rs +++ b/nautilus_core/common/src/messages/data.rs @@ -21,7 +21,8 @@ use nautilus_model::{ identifiers::{ClientId, Venue}, }; -// TOOD: redesign data messages for a tighter model +// TODO: redesign data messages for a tighter model +#[derive(Debug)] pub struct DataRequest { pub correlation_id: UUID4, pub client_id: ClientId, @@ -32,6 +33,7 @@ pub struct DataRequest { pub type Payload = Arc; +#[derive(Debug)] pub struct DataResponse { pub correlation_id: UUID4, pub client_id: ClientId, diff --git a/nautilus_core/common/src/msgbus/mod.rs b/nautilus_core/common/src/msgbus/mod.rs index e7cd3b40beff..466ace15642a 100644 --- a/nautilus_core/common/src/msgbus/mod.rs +++ b/nautilus_core/common/src/msgbus/mod.rs @@ -187,6 +187,12 @@ impl MessageBus { } } + /// Returns the message bus instances memory address. + #[must_use] + pub fn memory_address(&self) -> String { + format!("{:?}", std::ptr::from_ref(self)) + } + /// Returns the registered endpoint addresses. #[must_use] pub fn endpoints(&self) -> Vec<&str> { @@ -368,8 +374,8 @@ impl MessageBus { /// Publish a message to a topic. pub fn publish(&self, topic: &Ustr, message: &dyn Any) { log::trace!( - "Publishing topic '{topic}' {message:?} {:?}", - self as *const _ + "Publishing topic '{topic}' {message:?} {}", + self.memory_address() ); let matching_subs = self.matching_subscriptions(topic); @@ -380,10 +386,6 @@ impl MessageBus { sub.handler.0.handle(message); } } - - fn memory_address(&self) -> String { - format!("{:?}", self as *const _) - } } /// Data specific functions. diff --git a/nautilus_core/common/src/msgbus/stubs.rs b/nautilus_core/common/src/msgbus/stubs.rs index c7545f65d5e0..47c7adf07d8b 100644 --- a/nautilus_core/common/src/msgbus/stubs.rs +++ b/nautilus_core/common/src/msgbus/stubs.rs @@ -149,7 +149,7 @@ impl MessageHandler for MessageSavingHandler { let mut messages = self.messages.borrow_mut(); match message.downcast_ref::() { Some(m) => messages.push(m.clone()), - None => panic!("MessageSavingHandler: message type mismatch"), + None => panic!("MessageSavingHandler: message type mismatch {message:?}"), } } From 1ac0b45b277d080c9038608b3097bec6530a3ca4 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 11:23:55 +1000 Subject: [PATCH 14/72] Continue DataEngine in Rust --- nautilus_core/data/src/engine/mod.rs | 136 +++++++++++++++++++----- nautilus_core/data/src/engine/runner.rs | 4 +- 2 files changed, 110 insertions(+), 30 deletions(-) diff --git a/nautilus_core/data/src/engine/mod.rs b/nautilus_core/data/src/engine/mod.rs index 649e83d8b23c..a20b8331e791 100644 --- a/nautilus_core/data/src/engine/mod.rs +++ b/nautilus_core/data/src/engine/mod.rs @@ -48,6 +48,7 @@ use nautilus_model::{ trade::TradeTick, Data, DataType, }, + enums::RecordFlag, identifiers::{ClientId, InstrumentId, Venue}, instruments::{any::InstrumentAny, synthetic::SyntheticInstrument}, }; @@ -273,9 +274,21 @@ impl DataEngine { } } - /// Send a [`DataRequest`] to an endpoint that must be a data client implementation. pub fn execute(&mut self, msg: &dyn Any) { if let Some(cmd) = msg.downcast_ref::() { + match cmd.data_type.type_name() { + stringify!(OrderBookDelta) => { + // TODO: Check not synthetic + // TODO: Setup order book + } + stringify!(OrderBook) => { + // TODO: Check not synthetic + // TODO: Setup timer at interval + // TODO: Setup order book + } + type_name => log::error!("Cannot handle request, type {type_name} is unrecognized"), + } + if let Some(client) = self.clients.get_mut(&cmd.client_id) { client.execute(cmd.clone()); } else { @@ -289,10 +302,10 @@ impl DataEngine { } } + /// Send a [`DataRequest`] to an endpoint that must be a data client implementation. pub fn request(&self, req: DataRequest) { if let Some(client) = self.clients.get(&req.client_id) { - // TODO: We don't immediately need the response - let _ = client.request(req); + client.through_request(req); } else { log::error!( "Cannot handle request: no client found for {}", @@ -301,15 +314,15 @@ impl DataEngine { } } - /// TODO: Probably not required - /// Send a [`SubscriptionCommand`] to an endpoint that must be a data client implementation. - pub fn send_subscription_command(&self, message: SubscriptionCommand) { - if let Some(client) = self.get_client(&message.client_id, &message.venue) { - client.through_execute(message); + pub fn process(&mut self, data: &dyn Any) { + if let Some(instrument) = data.downcast_ref::() { + self.handle_instrument(instrument.clone()); + } else { + log::error!("Cannot process data {data:?}, type is unrecognized"); } } - pub fn process(&mut self, data: Data) { + pub fn process_data(&mut self, data: Data) { match data { Data::Delta(delta) => self.handle_delta(delta), Data::Deltas(deltas) => self.handle_deltas(deltas.deref().clone()), // TODO: Optimize @@ -321,7 +334,7 @@ impl DataEngine { } pub fn response(&self, resp: DataResponse) { - log::debug!("{}", format!("{RECV}{RES} response")); // TODO: Display for response + log::debug!("{}", format!("{RECV}{RES} {resp:?}")); match resp.data_type.type_name() { stringify!(InstrumentAny) => { @@ -344,7 +357,7 @@ impl DataEngine { Arc::downcast::>(resp.data.clone()).expect("Invalid response data"); self.handle_bars(bars); } - _ => {} // Nothing else to handle + type_name => log::error!("Cannot handle request, type {type_name} is unrecognized"), } self.msgbus.as_ref().borrow().send_response(resp); @@ -352,7 +365,6 @@ impl DataEngine { // -- DATA HANDLERS --------------------------------------------------------------------------- - // TODO: Fix all handlers to not use msgbus fn handle_instrument(&mut self, instrument: InstrumentAny) { if let Err(e) = self .cache @@ -369,12 +381,27 @@ impl DataEngine { } fn handle_delta(&mut self, delta: OrderBookDelta) { - // TODO: Manage buffered deltas - // TODO: Manage book + let deltas = if self.config.buffer_deltas { + let buffer_deltas = self + .buffered_deltas_map + .entry(delta.instrument_id) + .or_default(); + buffer_deltas.push(delta); + + if !RecordFlag::F_LAST.matches(delta.flags) { + return; // Not the last delta for event + } + + // TODO: Improve efficiency, the FFI API will go along with Cython + OrderBookDeltas::new(delta.instrument_id, buffer_deltas.clone()) + } else { + // TODO: Improve efficiency, the FFI API will go along with Cython + OrderBookDeltas::new(delta.instrument_id, vec![delta]) + }; let mut msgbus = self.msgbus.borrow_mut(); - let topic = msgbus.switchboard.get_delta_topic(delta.instrument_id); - msgbus.publish(&topic, &delta as &dyn Any); // TODO: Optimize + let topic = msgbus.switchboard.get_deltas_topic(deltas.instrument_id); + msgbus.publish(&topic, &deltas as &dyn Any); } fn handle_deltas(&mut self, deltas: OrderBookDeltas) { @@ -588,7 +615,7 @@ mod tests { fn cache() -> Rc> { // Ensure there is only ever one instance of the cache *per test* thread_local! { - static CACHE: OnceCell>> = OnceCell::new(); + static CACHE: OnceCell>> = const { OnceCell::new() }; } Rc::new(RefCell::new(Cache::default())) } @@ -597,7 +624,7 @@ mod tests { fn msgbus(trader_id: TraderId) -> Rc> { // Ensure there is only ever one instance of the message bus *per test* thread_local! { - static MSGBUS: OnceCell>> = OnceCell::new(); + static MSGBUS: OnceCell>> = const { OnceCell::new() }; } MSGBUS.with(|cell| { @@ -910,6 +937,60 @@ mod tests { assert!(data_engine.borrow().subscribed_bars().contains(&bar_type)); } + #[rstest] + fn test_process_instrument( + audusd_sim: CurrencyPair, + msgbus: Rc>, + switchboard: MessagingSwitchboard, + data_engine: Rc>, + data_client: DataClientAdapter, + ) { + let client_id = data_client.client_id; + let venue = data_client.venue; + data_engine.borrow_mut().register_client(data_client, None); + + let audusd_sim = InstrumentAny::CurrencyPair(audusd_sim); + let metadata = indexmap! { + "instrument_id".to_string() => audusd_sim.id().to_string(), + }; + let data_type = DataType::new(stringify!(InstrumentAny), Some(metadata)); + let cmd = SubscriptionCommand::new( + client_id, + venue, + data_type, + Action::Subscribe, + UUID4::new(), + UnixNanos::default(), + ); + + let endpoint = switchboard.data_engine_execute; + let handler = ShareableMessageHandler(Rc::new(SubscriptionCommandHandler { + id: endpoint, + data_engine: data_engine.clone(), + })); + msgbus.borrow_mut().register(endpoint, handler); + msgbus.borrow().send(&endpoint, &cmd as &dyn Any); + + let handler = get_message_saving_handler::(None); + { + let mut msgbus = msgbus.borrow_mut(); + let topic = msgbus.switchboard.get_instrument_topic(audusd_sim.id()); + msgbus.subscribe(topic, handler.clone(), None); + } + + let mut data_engine = data_engine.borrow_mut(); + data_engine.process(&audusd_sim as &dyn Any); + let cache = &data_engine.cache.borrow(); + let messages = get_saved_messages::(handler); + + assert_eq!( + cache.instrument(&audusd_sim.id()), + Some(audusd_sim.clone()).as_ref() + ); + assert_eq!(messages.len(), 1); + assert!(messages.contains(&audusd_sim)); + } + #[rstest] fn test_process_order_book_delta( audusd_sim: CurrencyPair, @@ -945,20 +1026,19 @@ mod tests { msgbus.borrow().send(&endpoint, &cmd as &dyn Any); let delta = stub_delta(); - let handler = get_message_saving_handler::(None); + let handler = get_message_saving_handler::(None); { let mut msgbus = msgbus.borrow_mut(); - let topic = msgbus.switchboard.get_delta_topic(delta.instrument_id); + let topic = msgbus.switchboard.get_deltas_topic(delta.instrument_id); msgbus.subscribe(topic, handler.clone(), None); } let mut data_engine = data_engine.borrow_mut(); - data_engine.process(Data::Delta(delta)); + data_engine.process_data(Data::Delta(delta)); let cache = &data_engine.cache.borrow(); - let messages = get_saved_messages::(handler); + let messages = get_saved_messages::(handler); assert_eq!(messages.len(), 1); - assert!(messages.contains(&delta)); } #[rstest] @@ -1005,7 +1085,7 @@ mod tests { } let mut data_engine = data_engine.borrow_mut(); - data_engine.process(Data::Deltas(deltas.clone())); + data_engine.process_data(Data::Deltas(deltas.clone())); let cache = &data_engine.cache.borrow(); let messages = get_saved_messages::(handler); @@ -1056,7 +1136,7 @@ mod tests { } let mut data_engine = data_engine.borrow_mut(); - data_engine.process(Data::Depth10(depth)); + data_engine.process_data(Data::Depth10(depth)); let cache = &data_engine.cache.borrow(); let messages = get_saved_messages::(handler); @@ -1106,7 +1186,7 @@ mod tests { } let mut data_engine = data_engine.borrow_mut(); - data_engine.process(Data::Quote(quote)); + data_engine.process_data(Data::Quote(quote)); let cache = &data_engine.cache.borrow(); let messages = get_saved_messages::(handler); @@ -1157,7 +1237,7 @@ mod tests { } let mut data_engine = data_engine.borrow_mut(); - data_engine.process(Data::Trade(trade)); + data_engine.process_data(Data::Trade(trade)); let cache = &data_engine.cache.borrow(); let messages = get_saved_messages::(handler); @@ -1208,7 +1288,7 @@ mod tests { } let mut data_engine = data_engine.borrow_mut(); - data_engine.process(Data::Bar(bar)); + data_engine.process_data(Data::Bar(bar)); let cache = &data_engine.cache.borrow(); let messages = get_saved_messages::(handler); diff --git a/nautilus_core/data/src/engine/runner.rs b/nautilus_core/data/src/engine/runner.rs index 1d3ce7305f30..8019d820795f 100644 --- a/nautilus_core/data/src/engine/runner.rs +++ b/nautilus_core/data/src/engine/runner.rs @@ -51,7 +51,7 @@ impl Runner for BacktestRunner { while let Some(resp) = self.queue.as_ref().borrow_mut().pop_front() { match resp { DataClientResponse::Response(resp) => engine.response(resp), - DataClientResponse::Data(data) => engine.process(data), + DataClientResponse::Data(data) => engine.process_data(data), } } } @@ -78,7 +78,7 @@ impl Runner for LiveRunner { while let Some(resp) = self.resp_rx.blocking_recv() { match resp { DataClientResponse::Response(resp) => engine.response(resp), - DataClientResponse::Data(data) => engine.process(data), + DataClientResponse::Data(data) => engine.process_data(data), } } } From 1ed6146b6696823c20575b726709f70effdbf6f2 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 15:14:15 +1000 Subject: [PATCH 15/72] Fix Bybit PositionSide enum --- nautilus_trader/adapters/bybit/common/enums.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nautilus_trader/adapters/bybit/common/enums.py b/nautilus_trader/adapters/bybit/common/enums.py index ebbc2d33a9b1..b9f58443cb9c 100644 --- a/nautilus_trader/adapters/bybit/common/enums.py +++ b/nautilus_trader/adapters/bybit/common/enums.py @@ -86,11 +86,14 @@ class BybitOptionType(Enum): @unique class BybitPositionSide(Enum): + FLAT = "" BUY = "Buy" SELL = "Sell" def parse_to_position_side(self) -> PositionSide: - if self == BybitPositionSide.BUY: + if self == BybitPositionSide.FLAT: + return PositionSide.FLAT + elif self == BybitPositionSide.BUY: return PositionSide.LONG elif self == BybitPositionSide.SELL: return PositionSide.SHORT From 0b613223d5585ebd60a736759096d3fe89db3688 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 15:30:35 +1000 Subject: [PATCH 16/72] Refine Bybit symbol handling --- RELEASES.md | 2 +- .../adapters/bybit/common/symbol.py | 2 +- nautilus_trader/adapters/bybit/data.py | 23 +++++++--------- nautilus_trader/adapters/bybit/execution.py | 26 +++++++++---------- .../adapters/bybit/schemas/instrument.py | 8 +++--- nautilus_trader/adapters/bybit/schemas/ws.py | 5 ++-- .../adapters/bybit/test_ws_decoders.py | 5 ++-- 7 files changed, 34 insertions(+), 37 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 829356cf92b3..df416f1ce767 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -11,7 +11,7 @@ Released on TBD (UTC). None ### Fixes -None +- Fixed Bybit position report parsing when position is flat (`BybitPositionSide` need to handle the empty string) --- diff --git a/nautilus_trader/adapters/bybit/common/symbol.py b/nautilus_trader/adapters/bybit/common/symbol.py index 065513c1b3ea..29285e746681 100644 --- a/nautilus_trader/adapters/bybit/common/symbol.py +++ b/nautilus_trader/adapters/bybit/common/symbol.py @@ -147,7 +147,7 @@ def is_option(self) -> bool: """ return self.product_type == BybitProductType.OPTION - def parse_as_nautilus(self) -> InstrumentId: + def to_instrument_id(self) -> InstrumentId: """ Parse the Bybit symbol into a Nautilus instrument ID. diff --git a/nautilus_trader/adapters/bybit/data.py b/nautilus_trader/adapters/bybit/data.py index 4f40cc2aa20d..bbab78b98737 100644 --- a/nautilus_trader/adapters/bybit/data.py +++ b/nautilus_trader/adapters/bybit/data.py @@ -376,10 +376,13 @@ async def _unsubscribe_bars(self, bar_type: BarType) -> None: self._topic_bar_type.pop(topic, None) await ws_client.unsubscribe_klines(bybit_symbol.raw_symbol, interval_str) - def _get_cached_instrument_id(self, symbol: str) -> InstrumentId: - bybit_symbol = BybitSymbol(symbol) - nautilus_instrument_id: InstrumentId = bybit_symbol.parse_as_nautilus() - return nautilus_instrument_id + def _get_cached_instrument_id( + self, + symbol: str, + product_type: BybitProductType, + ) -> InstrumentId: + bybit_symbol = BybitSymbol(f"{symbol}-{product_type.value.upper()}") + return bybit_symbol.to_instrument_id() async def _request(self, data_type: DataType, correlation_id: UUID4) -> None: if data_type.type == BybitTickerData: @@ -595,9 +598,7 @@ def _handle_ws_message(self, product_type: BybitProductType, raw: bytes) -> None def _handle_orderbook(self, product_type: BybitProductType, raw: bytes, topic: str) -> None: msg = self._decoder_ws_orderbook.decode(raw) - symbol = msg.data.s + f"-{product_type.value.upper()}" - instrument_id: InstrumentId = self._get_cached_instrument_id(symbol) - + instrument_id = self._get_cached_instrument_id(msg.data.s, product_type) instrument = self._cache.instrument(instrument_id) if instrument is None: self._log.error(f"Cannot parse order book data: no instrument for {instrument_id}") @@ -645,10 +646,8 @@ def _handle_ticker(self, product_type: BybitProductType, raw: bytes) -> None: msg = decoder.decode(raw) try: - symbol = msg.data.symbol + f"-{product_type.value.upper()}" - instrument_id: InstrumentId = self._get_cached_instrument_id(symbol) + instrument_id = self._get_cached_instrument_id(msg.data.symbol, product_type) instrument = self._cache.instrument(instrument_id) - if instrument is None: self._log.error(f"Cannot parse trade data: no instrument for {instrument_id}") return @@ -697,10 +696,8 @@ def _handle_trade(self, product_type: BybitProductType, raw: bytes) -> None: msg = self._decoder_ws_trade.decode(raw) try: for data in msg.data: - symbol = data.s + f"-{product_type.value.upper()}" - instrument_id: InstrumentId = self._get_cached_instrument_id(symbol) + instrument_id = self._get_cached_instrument_id(data.s, product_type) instrument = self._cache.instrument(instrument_id) - if instrument is None: self._log.error(f"Cannot parse trade data: no instrument for {instrument_id}") return diff --git a/nautilus_trader/adapters/bybit/execution.py b/nautilus_trader/adapters/bybit/execution.py index eb54e7386cbb..ccf8b191601a 100644 --- a/nautilus_trader/adapters/bybit/execution.py +++ b/nautilus_trader/adapters/bybit/execution.py @@ -267,7 +267,7 @@ async def generate_order_status_reports( report = bybit_order.parse_to_order_status_report( client_order_id=client_order_id, account_id=self.account_id, - instrument_id=bybit_symbol.parse_as_nautilus(), + instrument_id=bybit_symbol.to_instrument_id(), report_id=UUID4(), enum_parser=self._enum_parser, ts_init=self._clock.timestamp_ns(), @@ -379,7 +379,7 @@ async def generate_fill_reports( ) report = bybit_fill.parse_to_fill_report( account_id=self.account_id, - instrument_id=bybit_symbol.parse_as_nautilus(), + instrument_id=bybit_symbol.to_instrument_id(), report_id=UUID4(), enum_parser=self._enum_parser, ts_init=self._clock.timestamp_ns(), @@ -412,8 +412,6 @@ async def generate_position_status_reports( bybit_symbol.raw_symbol, ) for position in positions: - # Uncomment for development - # self._log.info(f"Generating report {position}", LogColor.MAGENTA) position_report = position.parse_to_position_status_report( account_id=self.account_id, instrument_id=instrument_id, @@ -429,14 +427,11 @@ async def generate_position_status_reports( continue # No positions on spot positions = await self._http_account.query_position_info(product_type) for position in positions: - # Uncomment for development - # self._log.info(f"Generating report {position}", LogColor.MAGENTA) - instr: InstrumentId = BybitSymbol( - position.symbol + "-" + product_type.value.upper(), - ).parse_as_nautilus() + symbol = position.symbol + bybit_symbol = BybitSymbol(f"{symbol}-{product_type.value.upper()}") position_report = position.parse_to_position_status_report( account_id=self.account_id, - instrument_id=instr, + instrument_id=bybit_symbol.to_instrument_id(), report_id=UUID4(), ts_init=self._clock.timestamp_ns(), ) @@ -451,10 +446,13 @@ async def generate_position_status_reports( return reports - def _get_cached_instrument_id(self, symbol: str, category: str) -> InstrumentId: - bybit_symbol = BybitSymbol(symbol + f"-{category.upper()}") - nautilus_instrument_id: InstrumentId = bybit_symbol.parse_as_nautilus() - return nautilus_instrument_id + def _get_cached_instrument_id( + self, + symbol: str, + product_type: BybitProductType, + ) -> InstrumentId: + bybit_symbol = BybitSymbol(f"{symbol}-{product_type.value.upper()}") + return bybit_symbol.to_instrument_id() def _get_cache_active_symbols(self) -> set[str]: # Check cache for all active orders diff --git a/nautilus_trader/adapters/bybit/schemas/instrument.py b/nautilus_trader/adapters/bybit/schemas/instrument.py index b83e2036f81d..e647772291ad 100644 --- a/nautilus_trader/adapters/bybit/schemas/instrument.py +++ b/nautilus_trader/adapters/bybit/schemas/instrument.py @@ -63,7 +63,7 @@ def parse_to_instrument( assert base_currency.code == self.baseCoin assert quote_currency.code == self.quoteCoin bybit_symbol = BybitSymbol(self.symbol + "-SPOT") - instrument_id = bybit_symbol.parse_as_nautilus() + instrument_id = bybit_symbol.to_instrument_id() price_increment = Price.from_str(self.priceFilter.tickSize) size_increment = Quantity.from_str(self.lotSizeFilter.basePrecision) lot_size = Quantity.from_str(self.lotSizeFilter.basePrecision) @@ -128,7 +128,7 @@ def parse_to_instrument( assert base_currency.code == self.baseCoin assert quote_currency.code == self.quoteCoin bybit_symbol = BybitSymbol(self.symbol + "-LINEAR") - instrument_id = bybit_symbol.parse_as_nautilus() + instrument_id = bybit_symbol.to_instrument_id() if self.settleCoin == self.baseCoin: settlement_currency = base_currency elif self.settleCoin == self.quoteCoin: @@ -233,7 +233,7 @@ def parse_to_instrument( assert base_currency.code == self.baseCoin assert quote_currency.code == self.quoteCoin bybit_symbol = BybitSymbol(self.symbol + "-INVERSE") - instrument_id = bybit_symbol.parse_as_nautilus() + instrument_id = bybit_symbol.to_instrument_id() if self.settleCoin == self.baseCoin: settlement_currency = base_currency elif self.settleCoin == self.quoteCoin: @@ -328,7 +328,7 @@ def parse_to_instrument( ) -> OptionsContract: assert quote_currency.code == self.quoteCoin bybit_symbol = BybitSymbol(self.symbol + "-OPTION") - instrument_id = bybit_symbol.parse_as_nautilus() + instrument_id = bybit_symbol.to_instrument_id() price_increment = Price.from_str(self.priceFilter.tickSize) if self.optionsType == BybitOptionType.CALL: option_kind = OptionKind.CALL diff --git a/nautilus_trader/adapters/bybit/schemas/ws.py b/nautilus_trader/adapters/bybit/schemas/ws.py index 425bb26f5a22..14e4d9b552a9 100644 --- a/nautilus_trader/adapters/bybit/schemas/ws.py +++ b/nautilus_trader/adapters/bybit/schemas/ws.py @@ -25,6 +25,7 @@ from nautilus_trader.adapters.bybit.common.enums import BybitOrderStatus from nautilus_trader.adapters.bybit.common.enums import BybitOrderType from nautilus_trader.adapters.bybit.common.enums import BybitPositionIdx +from nautilus_trader.adapters.bybit.common.enums import BybitProductType from nautilus_trader.adapters.bybit.common.enums import BybitStopOrderType from nautilus_trader.adapters.bybit.common.enums import BybitTimeInForce from nautilus_trader.adapters.bybit.common.enums import BybitTriggerDirection @@ -600,7 +601,7 @@ class BybitWsAccountPositionMsg(msgspec.Struct): class BybitWsAccountOrder(msgspec.Struct): - category: str + category: BybitProductType symbol: str orderId: str side: BybitOrderSide @@ -709,7 +710,7 @@ class BybitWsAccountOrderMsg(msgspec.Struct): class BybitWsAccountExecution(msgspec.Struct): - category: str + category: BybitProductType symbol: str execFee: str execId: str diff --git a/tests/integration_tests/adapters/bybit/test_ws_decoders.py b/tests/integration_tests/adapters/bybit/test_ws_decoders.py index 0d78c3e153de..b9fa9dffd78e 100644 --- a/tests/integration_tests/adapters/bybit/test_ws_decoders.py +++ b/tests/integration_tests/adapters/bybit/test_ws_decoders.py @@ -23,6 +23,7 @@ from nautilus_trader.adapters.bybit.common.enums import BybitOrderStatus from nautilus_trader.adapters.bybit.common.enums import BybitOrderType from nautilus_trader.adapters.bybit.common.enums import BybitPositionIdx +from nautilus_trader.adapters.bybit.common.enums import BybitProductType from nautilus_trader.adapters.bybit.common.enums import BybitStopOrderType from nautilus_trader.adapters.bybit.common.enums import BybitTimeInForce from nautilus_trader.adapters.bybit.common.enums import BybitTriggerDirection @@ -455,7 +456,7 @@ def test_ws_private_execution(self): decoder = msgspec.json.Decoder(BybitWsAccountExecutionMsg) result = decoder.decode(item) target_data = BybitWsAccountExecution( - category="linear", + category=BybitProductType.LINEAR, symbol="XRPUSDT", execFee="0.005061", execId="7e2ae69c-4edf-5800-a352-893d52b446aa", @@ -498,6 +499,7 @@ def test_ws_private_order(self): decoder = msgspec.json.Decoder(BybitWsAccountOrderMsg) result = decoder.decode(item) target_data = BybitWsAccountOrder( + category=BybitProductType.OPTION, symbol="ETH-30DEC22-1400-C", orderId="5cf98598-39a7-459e-97bf-76ca765ee020", side=BybitOrderSide.SELL, @@ -534,7 +536,6 @@ def test_ws_private_order(self): triggerDirection=BybitTriggerDirection.RISES_TO, triggerBy=BybitTriggerType.NONE, closeOnTrigger=False, - category="option", placeType="price", smpType="None", smpGroup=0, From 3a3d1d5f9f924be88c20b3c711a4606113e1ee47 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 16:01:25 +1000 Subject: [PATCH 17/72] Improve Binance position reports when flat --- RELEASES.md | 6 +++--- .../adapters/binance/common/execution.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index df416f1ce767..cb94df91d975 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,15 +3,15 @@ Released on TBD (UTC). ### Enhancements -- Improve `BinanceExecutionClient` position report requests (can now filter by instrument) -- Improve `BybitExecutionClient` position report requests (can now filter by instrument) +- Improve `BinanceExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) +- Improve `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improve `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions ### Breaking Changes None ### Fixes -- Fixed Bybit position report parsing when position is flat (`BybitPositionSide` need to handle the empty string) +- Fixed Bybit position report parsing when position is flat (`BybitPositionSide` now correctly handles the empty string) --- diff --git a/nautilus_trader/adapters/binance/common/execution.py b/nautilus_trader/adapters/binance/common/execution.py index 1173e326181f..7166c40c728c 100644 --- a/nautilus_trader/adapters/binance/common/execution.py +++ b/nautilus_trader/adapters/binance/common/execution.py @@ -59,6 +59,7 @@ from nautilus_trader.model.enums import OmsType from nautilus_trader.model.enums import OrderSide from nautilus_trader.model.enums import OrderType +from nautilus_trader.model.enums import PositionSide from nautilus_trader.model.enums import TimeInForce from nautilus_trader.model.enums import TrailingOffsetType from nautilus_trader.model.enums import TriggerType @@ -72,6 +73,7 @@ from nautilus_trader.model.identifiers import Venue from nautilus_trader.model.identifiers import VenueOrderId from nautilus_trader.model.objects import Price +from nautilus_trader.model.objects import Quantity from nautilus_trader.model.orders import LimitOrder from nautilus_trader.model.orders import MarketOrder from nautilus_trader.model.orders import Order @@ -548,6 +550,18 @@ async def generate_position_status_reports( self._log.info(f"Requesting PositionStatusReport for {instrument_id}") symbol = instrument_id.symbol.value reports = await self._get_binance_position_status_reports(symbol) + if not reports: + now = self._clock.timestamp_ns() + report = PositionStatusReport( + account_id=self.account_id, + instrument_id=instrument_id, + position_side=PositionSide.FLAT, + quantity=Quantity.zero(), + report_id=UUID4(), + ts_last=now, + ts_init=now, + ) + reports = [report] else: self._log.info("Requesting PositionStatusReports...") reports = await self._get_binance_position_status_reports() From 8e1014680ed8714a1db3de26d92f5021086cd33c Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 16:22:37 +1000 Subject: [PATCH 18/72] Add LiveExecEngineConfig.generate_missing_orders --- RELEASES.md | 1 + nautilus_trader/live/config.py | 4 ++++ nautilus_trader/live/execution_engine.py | 10 +++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index cb94df91d975..785c2db2068c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,6 +3,7 @@ Released on TBD (UTC). ### Enhancements +- Added `LiveExecEngineConfig.generate_missing_orders` reconciliation config option to align internal and external position states - Improve `BinanceExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improve `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improve `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions diff --git a/nautilus_trader/live/config.py b/nautilus_trader/live/config.py index 2f3ad4d3d7d4..70853850c841 100644 --- a/nautilus_trader/live/config.py +++ b/nautilus_trader/live/config.py @@ -84,6 +84,9 @@ class LiveExecEngineConfig(ExecEngineConfig, frozen=True): If position status reports are filtered from reconciliation. This may be applicable when other nodes are trading the same instrument(s), on the same account - which could cause conflicts in position status. + generate_missing_orders : bool, default True + If MARKET order events will be generated during reconciliation to align discrepancies + between internal and external positions. inflight_check_interval_ms : NonNegativeInt, default 2_000 The interval (milliseconds) between checking whether in-flight orders have exceeded their time-in-flight threshold. @@ -102,6 +105,7 @@ class LiveExecEngineConfig(ExecEngineConfig, frozen=True): reconciliation_lookback_mins: NonNegativeInt | None = None filter_unclaimed_external_orders: bool = False filter_position_reports: bool = False + generate_missing_orders: bool = True inflight_check_interval_ms: NonNegativeInt = 2_000 inflight_check_threshold_ms: NonNegativeInt = 5_000 qsize: PositiveInt = 100_000 diff --git a/nautilus_trader/live/execution_engine.py b/nautilus_trader/live/execution_engine.py index a933e77fc5c3..dd42c3258da9 100644 --- a/nautilus_trader/live/execution_engine.py +++ b/nautilus_trader/live/execution_engine.py @@ -128,6 +128,7 @@ def __init__( self.reconciliation_lookback_mins: int = config.reconciliation_lookback_mins or 0 self.filter_unclaimed_external_orders: bool = config.filter_unclaimed_external_orders self.filter_position_reports: bool = config.filter_position_reports + self.generate_missing_orders: bool = config.generate_missing_orders self.inflight_check_interval_ms: int = config.inflight_check_interval_ms self.inflight_check_threshold_ms: int = config.inflight_check_threshold_ms self._inflight_check_threshold_ns: int = millis_to_nanos(self.inflight_check_threshold_ms) @@ -494,7 +495,7 @@ async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # noqa: C9 if self.filter_position_reports: self._log.warning( - f"Filtering position reports enabled. Skipping further reconciliation for {client_id}", + "`filter_position_reports` enabled, skipping further reconciliation", ) continue @@ -848,6 +849,13 @@ def _reconcile_position_report_netting(self, report: PositionStatusReport) -> bo self._log.info(f"{position_signed_decimal_qty=}", LogColor.BLUE) if position_signed_decimal_qty != report.signed_decimal_qty: + if not self.generate_missing_orders: + self._log.warning( + f"Discrepancy for {report.instrument_id} position " + "when `generate_missing_orders` disabled, skipping further reconciliation", + ) + return False + diff = abs(position_signed_decimal_qty - report.signed_decimal_qty) diff_quantity = Quantity(diff, instrument.size_precision) self._log.info(f"{diff_quantity=}", LogColor.BLUE) From a295dbe7638e7c7fd929a148392bd68757f82d80 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 16:54:01 +1000 Subject: [PATCH 19/72] Refine LiveExecutionEngine reconciliation procedure --- nautilus_trader/live/execution_engine.py | 35 +++++++++++------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/nautilus_trader/live/execution_engine.py b/nautilus_trader/live/execution_engine.py index dd42c3258da9..a790a7879ca2 100644 --- a/nautilus_trader/live/execution_engine.py +++ b/nautilus_trader/live/execution_engine.py @@ -485,29 +485,24 @@ async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # noqa: C9 client_id = mass_status.client_id venue = mass_status.venue result = self._reconcile_mass_status(mass_status) + positions = self._cache.positions_open(venue) if result: - results.append(result) - self._log.info(f"Reconciliation for {client_id} succeeded", LogColor.GREEN) - continue - - self._log.warning(f"Reconciliation for {client_id} failed") - - if self.filter_position_reports: - self._log.warning( - "`filter_position_reports` enabled, skipping further reconciliation", - ) - continue - - # Reconcile specific positions open - positions = self._cache.positions_open(venue) - if not positions: - self._log.warning(f"No cached open positions found for {venue}") - results.append(False) - continue + if not positions: + results.append(result) + self._log.info(f"Reconciliation for {client_id} succeeded", LogColor.GREEN) + continue + else: # Reconciliation failed + self._log.warning(f"Reconciliation for {client_id} initially failed") + if self.filter_position_reports: + self._log.warning( + "`filter_position_reports` enabled, skipping further reconciliation", + ) + continue client = self._clients.get(client_id) + # Reconcile specific internal open positions report_tasks: list[asyncio.Task] = [] for position in positions: instrument_id = position.instrument_id @@ -518,7 +513,9 @@ async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # noqa: C9 report_tasks.append(client.generate_position_report(instrument_id)) if not report_tasks: - self._log.warning(f"No new position reports received for {venue}") + self._log.warning( + f"No specific position reports received for {venue}, skipping further reconciliation", + ) results.append(False) continue From 026369c2f992fcfd9ae9fc56fec27bd5654a0ce1 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 17:20:41 +1000 Subject: [PATCH 20/72] Refine LiveExecutionEngine reconciliation --- nautilus_trader/live/execution_engine.py | 27 ++++++++++++------------ 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/nautilus_trader/live/execution_engine.py b/nautilus_trader/live/execution_engine.py index a790a7879ca2..b7f413ed80e8 100644 --- a/nautilus_trader/live/execution_engine.py +++ b/nautilus_trader/live/execution_engine.py @@ -510,29 +510,28 @@ async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # noqa: C9 self._log.debug(f"Position {instrument_id} for {client_id} already reconciled") continue # Already reconciled self._log.info(f"{position} pending reconciliation") - report_tasks.append(client.generate_position_report(instrument_id)) + report_tasks.append(client.generate_position_status_reports(instrument_id)) if not report_tasks: - self._log.warning( - f"No specific position reports received for {venue}, skipping further reconciliation", - ) - results.append(False) + # No specific position reports to continue reconciliation continue self._log.info( f"Awaiting reconciliation for {len(report_tasks)} position reports for {client_id}", ) - position_reports = await asyncio.gather(*report_tasks) position_results: list[bool] = [] - for report in position_reports: - position_result = self._reconcile_position_report(report) - instrument_id = report.instrument_id - if position_result: - self._log.info(f"Reconciliation for {instrument_id} succeeded", LogColor.GREEN) - else: - self._log.warning(f"Reconciliation for {instrument_id} failed") - position_results.append(position_result) + for task_result in await asyncio.gather(*report_tasks): + for report in task_result: + position_result = self._reconcile_position_report(report) + if position_result: + self._log.info( + f"Reconciliation for {report.instrument_id} succeeded", + LogColor.GREEN, + ) + else: + self._log.warning(f"Reconciliation for {report.instrument_id} failed") + position_results.append(position_result) results.append(all(position_results)) From 4d75b8992a58f62913f67fe46011fc54445efbcd Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 17:49:29 +1000 Subject: [PATCH 21/72] Refine LiveExecutionEngine reconciliation procedure --- nautilus_trader/live/execution_engine.py | 68 +++++++++++------------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/nautilus_trader/live/execution_engine.py b/nautilus_trader/live/execution_engine.py index b7f413ed80e8..fbd835fc7a6f 100644 --- a/nautilus_trader/live/execution_engine.py +++ b/nautilus_trader/live/execution_engine.py @@ -55,7 +55,9 @@ from nautilus_trader.model.events import OrderRejected from nautilus_trader.model.events import OrderTriggered from nautilus_trader.model.events import OrderUpdated +from nautilus_trader.model.identifiers import ClientId from nautilus_trader.model.identifiers import ClientOrderId +from nautilus_trader.model.identifiers import InstrumentId from nautilus_trader.model.identifiers import PositionId from nautilus_trader.model.identifiers import StrategyId from nautilus_trader.model.identifiers import TradeId @@ -435,7 +437,13 @@ async def _check_inflight_orders(self) -> None: # -- RECONCILIATION ------------------------------------------------------------------------------- - async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # noqa: C901 (too complex) + def _log_reconciliation_result(self, value: ClientId | InstrumentId, result: bool) -> None: + if result: + self._log.info(f"Reconciliation for {value} succeeded", LogColor.GREEN) + else: + self._log.warning(f"Reconciliation for {value} failed") + + async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # (too complex) """ Reconcile the internal execution state with all execution clients (external state). @@ -485,26 +493,20 @@ async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # noqa: C9 client_id = mass_status.client_id venue = mass_status.venue result = self._reconcile_mass_status(mass_status) - positions = self._cache.positions_open(venue) - if result: - if not positions: - results.append(result) - self._log.info(f"Reconciliation for {client_id} succeeded", LogColor.GREEN) - continue - else: # Reconciliation failed - self._log.warning(f"Reconciliation for {client_id} initially failed") - if self.filter_position_reports: - self._log.warning( - "`filter_position_reports` enabled, skipping further reconciliation", - ) - continue + if not result and self.filter_position_reports: + self._log_reconciliation_result(client_id, result) + results.append(result) + self._log.warning( + "`filter_position_reports` enabled, skipping further reconciliation", + ) + continue - client = self._clients.get(client_id) + client = self._clients[client_id] - # Reconcile specific internal open positions + # Check internal and external position reconciliation report_tasks: list[asyncio.Task] = [] - for position in positions: + for position in self._cache.positions_open(venue): instrument_id = position.instrument_id if instrument_id in mass_status.position_reports: self._log.debug(f"Position {instrument_id} for {client_id} already reconciled") @@ -512,28 +514,20 @@ async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # noqa: C9 self._log.info(f"{position} pending reconciliation") report_tasks.append(client.generate_position_status_reports(instrument_id)) - if not report_tasks: - # No specific position reports to continue reconciliation - continue + if report_tasks: + # Reconcile specific internal open positions + self._log.info(f"Awaiting {len(report_tasks)} position reports for {client_id}") + position_results: list[bool] = [] + for task_result in await asyncio.gather(*report_tasks): + for report in task_result: + position_result = self._reconcile_position_report(report) + self._log_reconciliation_result(report.instrument_id, position_result) + position_results.append(position_result) - self._log.info( - f"Awaiting reconciliation for {len(report_tasks)} position reports for {client_id}", - ) + result = all(position_results) - position_results: list[bool] = [] - for task_result in await asyncio.gather(*report_tasks): - for report in task_result: - position_result = self._reconcile_position_report(report) - if position_result: - self._log.info( - f"Reconciliation for {report.instrument_id} succeeded", - LogColor.GREEN, - ) - else: - self._log.warning(f"Reconciliation for {report.instrument_id} failed") - position_results.append(position_result) - - results.append(all(position_results)) + self._log_reconciliation_result(client_id, result) + results.append(result) return all(results) From 8b62fe67a614e283bcb00d73c624f73793666e54 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 20:09:23 +1000 Subject: [PATCH 22/72] Add Position.has_trade_id --- nautilus_trader/model/position.pxd | 1 + nautilus_trader/model/position.pyx | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/nautilus_trader/model/position.pxd b/nautilus_trader/model/position.pxd index 10af6655e9e5..3ecae896f9b0 100644 --- a/nautilus_trader/model/position.pxd +++ b/nautilus_trader/model/position.pxd @@ -105,6 +105,7 @@ cdef class Position: cdef list events_c(self) cdef OrderFilled last_event_c(self) cdef TradeId last_trade_id_c(self) + cdef bint has_trade_id_c(self, TradeId trade_id) cdef int event_count_c(self) cdef bint is_long_c(self) cdef bint is_short_c(self) diff --git a/nautilus_trader/model/position.pyx b/nautilus_trader/model/position.pyx index 18809949060c..804801a7397f 100644 --- a/nautilus_trader/model/position.pyx +++ b/nautilus_trader/model/position.pyx @@ -179,6 +179,10 @@ cdef class Position: cdef TradeId last_trade_id_c(self): return self._events[-1].trade_id + cdef bint has_trade_id_c(self, TradeId trade_id): + Condition.not_none(trade_id, "trade_id") + return trade_id in self._trade_ids + cdef int event_count_c(self): return len(self._events) From 4d000f6f0af448f2a3bd58327ee5881b04d0ca27 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 20:09:29 +1000 Subject: [PATCH 23/72] Remove redundant comment --- nautilus_trader/live/execution_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nautilus_trader/live/execution_engine.py b/nautilus_trader/live/execution_engine.py index fbd835fc7a6f..ffef60b8f4e2 100644 --- a/nautilus_trader/live/execution_engine.py +++ b/nautilus_trader/live/execution_engine.py @@ -443,7 +443,7 @@ def _log_reconciliation_result(self, value: ClientId | InstrumentId, result: boo else: self._log.warning(f"Reconciliation for {value} failed") - async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: # (too complex) + async def reconcile_state(self, timeout_secs: float = 10.0) -> bool: """ Reconcile the internal execution state with all execution clients (external state). From 64796c567b30cf0e4042bcc4bb08655640a4ba3a Mon Sep 17 00:00:00 2001 From: Evgenii Prusov <114025336+evgenii-prusov@users.noreply.github.com> Date: Sun, 11 Aug 2024 08:34:43 -0400 Subject: [PATCH 24/72] Change VolumeWeightedAveragePrice calculation formula (#1842) Price changed from simple close to the average of the high, low, and close price (typical price). Sources of indicator algorithm: - https://www.investopedia.com/terms/v/vwap.asp - https://www.interactivebrokers.com/campus/ibkr-quant-news/vwap-tutorial-calculation-uses-and-limitations-part-i/ - https://www.investing.com/academy/analysis/vwap-formula/ --- nautilus_core/indicators/src/average/vwap.rs | 10 ++++------ nautilus_trader/indicators/vwap.pyx | 6 +++++- tests/unit_tests/indicators/test_vwap.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/nautilus_core/indicators/src/average/vwap.rs b/nautilus_core/indicators/src/average/vwap.rs index adf4737a82af..8dfab07725e0 100644 --- a/nautilus_core/indicators/src/average/vwap.rs +++ b/nautilus_core/indicators/src/average/vwap.rs @@ -54,11 +54,9 @@ impl Indicator for VolumeWeightedAveragePrice { } fn handle_bar(&mut self, bar: &Bar) { - self.update_raw( - (&bar.close).into(), - (&bar.volume).into(), - bar.ts_init.as_f64(), - ); + let typical_price = (bar.close.as_f64() + bar.high.as_f64() + bar.low.as_f64()) / 3.0; + + self.update_raw(typical_price, (&bar.volume).into(), bar.ts_init.as_f64()); } fn reset(&mut self) { @@ -173,7 +171,7 @@ mod tests { bar_ethusdt_binance_minute_bid: Bar, ) { indicator_vwap.handle_bar(&bar_ethusdt_binance_minute_bid); - assert_eq!(indicator_vwap.value, 1522.0); + assert_eq!(indicator_vwap.value, 1522.333333333333); assert!(indicator_vwap.initialized); } diff --git a/nautilus_trader/indicators/vwap.pyx b/nautilus_trader/indicators/vwap.pyx index 9d85c20dc023..1a2392ba92f4 100644 --- a/nautilus_trader/indicators/vwap.pyx +++ b/nautilus_trader/indicators/vwap.pyx @@ -47,7 +47,11 @@ cdef class VolumeWeightedAveragePrice(Indicator): Condition.not_none(bar, "bar") self.update_raw( - bar.close.as_double(), + ( + bar.close.as_double() + + bar.high.as_double() + + bar.low.as_double() + ) / 3.0, bar.volume.as_double(), pd.Timestamp(bar.ts_init, tz="UTC"), ) diff --git a/tests/unit_tests/indicators/test_vwap.py b/tests/unit_tests/indicators/test_vwap.py index 172266dee396..ee9373535137 100644 --- a/tests/unit_tests/indicators/test_vwap.py +++ b/tests/unit_tests/indicators/test_vwap.py @@ -62,7 +62,7 @@ def test_handle_bar_updates_indicator(self): # Assert assert indicator.has_inputs - assert indicator.value == 1.00003 + assert indicator.value == 1.0000266666666666 def test_value_with_one_input_returns_expected_value(self): # Arrange From 71063529ac3ee05df821f3809fcabec6d1e6d77b Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 11 Aug 2024 22:37:51 +1000 Subject: [PATCH 25/72] Update release notes --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 785c2db2068c..ee83599fab39 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,7 +9,7 @@ Released on TBD (UTC). - Improve `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions ### Breaking Changes -None +- Changed `VolumeWeightedAveragePrice` calculation formula to use each bars "typical" price (#1842), thanks @evgenii-prusov ### Fixes - Fixed Bybit position report parsing when position is flat (`BybitPositionSide` now correctly handles the empty string) From 3d6bce32cf82df93f57a42785490e7dfacb48860 Mon Sep 17 00:00:00 2001 From: Filip Macek Date: Sun, 11 Aug 2024 23:55:39 +0200 Subject: [PATCH 26/72] Add linked orders check in OrderMatchingEngine (#1843) --- nautilus_core/backtest/src/matching_engine.rs | 202 +++++++++++++----- nautilus_core/execution/src/matching_core.rs | 6 + nautilus_core/model/src/orders/any.rs | 15 ++ nautilus_core/model/src/orders/stubs.rs | 59 ++++- 4 files changed, 219 insertions(+), 63 deletions(-) diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index efedf0d4f48b..dda6106a3fc1 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -300,6 +300,29 @@ impl OrderMatchingEngine { } } } + + if let Some(linked_order_ids) = order.linked_order_ids() { + for client_order_id in linked_order_ids { + match cache_borrow.order(&client_order_id) { + Some(contingent_order) + if (order.contingency_type().unwrap() == ContingencyType::Oco + || order.contingency_type().unwrap() + == ContingencyType::Ouo) + && !order.is_closed() + && contingent_order.is_closed() => + { + self.generate_order_rejected( + order, + format!("Contingent order {} already closed", client_order_id) + .into(), + ); + return; + } + None => panic!("Cannot find contingent order for {}", client_order_id), + _ => {} + } + } + } } // Check fo valid order quantity precision @@ -828,19 +851,17 @@ mod tests { }; use nautilus_core::{nanos::UnixNanos, time::AtomicTime, uuid::UUID4}; use nautilus_model::{ - enums::{ - AccountType, BookType, ContingencyType, OmsType, OrderSide, TimeInForce, TriggerType, + enums::{AccountType, BookType, ContingencyType, OmsType, OrderSide, TimeInForce}, + events::order::{ + rejected::OrderRejectedBuilder, OrderEventAny, OrderEventType, OrderRejected, }, - events::order::{OrderEventAny, OrderEventType, OrderRejected}, identifiers::{AccountId, ClientOrderId, StrategyId, TraderId}, instruments::{ any::InstrumentAny, equity::Equity, stubs::{futures_contract_es, *}, }, - orders::{ - any::OrderAny, market::MarketOrder, stop_market::StopMarketOrder, stubs::TestOrderStubs, - }, + orders::{any::OrderAny, market::MarketOrder, stubs::TestOrderStubs}, types::{price::Price, quantity::Quantity}, }; use rstest::{fixture, rstest}; @@ -891,7 +912,19 @@ mod tests { InstrumentAny::FuturesContract(futures_contract_es(Some(activation), Some(expiration))) } - // -- HELPERS --------------------------------------------------------------------------------- + #[fixture] + fn engine_config() -> OrderMatchingEngineConfig { + OrderMatchingEngineConfig { + bar_execution: false, + reject_stop_orders: false, + support_gtd_orders: false, + support_contingent_orders: true, + use_position_ids: false, + use_random_ids: false, + use_reduce_only: true, + } + } + // -- HELPERS --------------------------------------------------------------------------- fn get_order_matching_engine( instrument: InstrumentAny, @@ -1113,6 +1146,9 @@ mod tests { None, None, None, + None, + None, + None, ); engine.process_order(&stop_order, account_id); @@ -1178,6 +1214,7 @@ mod tests { account_id: AccountId, time: AtomicTime, instrument_es: InstrumentAny, + engine_config: OrderMatchingEngineConfig, ) { // Register saving message handler to exec engine endpoint msgbus.register( @@ -1185,22 +1222,12 @@ mod tests { order_event_handler.clone(), ); - // Create engine (with reduce_only option) and process order - let config = OrderMatchingEngineConfig { - use_reduce_only: true, - bar_execution: false, - reject_stop_orders: false, - support_gtd_orders: false, - support_contingent_orders: false, - use_position_ids: false, - use_random_ids: false, - }; let mut engine = get_order_matching_engine( instrument_es.clone(), Rc::new(msgbus), None, None, - Some(config), + Some(engine_config), ); let market_order = TestOrderStubs::market_order_reduce( instrument_es.id(), @@ -1230,6 +1257,7 @@ mod tests { account_id: AccountId, time: AtomicTime, instrument_es: InstrumentAny, + engine_config: OrderMatchingEngineConfig, ) { // Register saving message handler to exec engine endpoint msgbus.register( @@ -1237,23 +1265,13 @@ mod tests { order_event_handler.clone(), ); - // Create engine (with reduce_only option) and process order - let config = OrderMatchingEngineConfig { - use_reduce_only: false, - bar_execution: false, - reject_stop_orders: false, - support_gtd_orders: false, - support_contingent_orders: true, - use_position_ids: false, - use_random_ids: false, - }; let cache = Rc::new(RefCell::new(Cache::default())); let mut engine = get_order_matching_engine( instrument_es.clone(), Rc::new(msgbus), Some(cache.clone()), None, - Some(config), + Some(engine_config), ); let entry_client_order_id = ClientOrderId::from("O-19700101-000000-001-001-1"); @@ -1291,35 +1309,17 @@ mod tests { .unwrap(); // Create stop loss order - let stop_order = OrderAny::StopMarket( - StopMarketOrder::new( - entry_order.trader_id(), - entry_order.strategy_id(), - entry_order.instrument_id(), - stop_loss_client_order_id, - OrderSide::Sell, - entry_order.quantity(), - Price::from("0.95"), - TriggerType::BidAsk, - TimeInForce::Gtc, - None, - true, - false, - None, - None, - None, - Some(ContingencyType::Oto), - None, - None, - Some(entry_client_order_id), // <- parent order id set from entry order - None, - None, - None, - None, - UUID4::new(), - UnixNanos::default(), - ) - .unwrap(), + let stop_order = TestOrderStubs::stop_market_order( + instrument_es.id(), + OrderSide::Sell, + Price::from("0.95"), + Quantity::from(1), + None, + Some(ContingencyType::Oto), + Some(stop_loss_client_order_id), + None, + Some(entry_client_order_id), + None, ); // Make it Accepted let accepted_stop_order = TestOrderStubs::make_accepted_order(&stop_order); @@ -1343,4 +1343,90 @@ mod tests { Ustr::from(format!("Rejected OTO order from {entry_client_order_id}").as_str()) ); } + + #[rstest] + fn test_process_order_when_closed_linked_order( + mut msgbus: MessageBus, + order_event_handler: ShareableMessageHandler, + account_id: AccountId, + time: AtomicTime, + instrument_es: InstrumentAny, + engine_config: OrderMatchingEngineConfig, + ) { + // Register saving message handler to exec engine endpoint + msgbus.register( + msgbus.switchboard.exec_engine_process, + order_event_handler.clone(), + ); + + let cache = Rc::new(RefCell::new(Cache::default())); + let mut engine = get_order_matching_engine( + instrument_es.clone(), + Rc::new(msgbus), + Some(cache.clone()), + None, + Some(engine_config), + ); + + let stop_loss_client_order_id = ClientOrderId::from("O-19700101-000000-001-001-2"); + let take_profit_client_order_id = ClientOrderId::from("O-19700101-000000-001-001-3"); + // Create two linked orders: stop loss and take profit + let mut stop_loss_order = TestOrderStubs::stop_market_order( + instrument_es.id(), + OrderSide::Sell, + Price::from("0.95"), + Quantity::from(1), + None, + Some(ContingencyType::Oco), + Some(stop_loss_client_order_id), + None, + None, + Some(vec![take_profit_client_order_id]), + ); + let take_profit_order = TestOrderStubs::market_if_touched_order( + instrument_es.id(), + OrderSide::Sell, + Price::from("1.1"), + Quantity::from(1), + None, + Some(ContingencyType::Oco), + Some(take_profit_client_order_id), + None, + Some(vec![stop_loss_client_order_id]), + ); + // Set stop loss order status to Rejected with proper event + let rejected_event: OrderRejected = OrderRejectedBuilder::default() + .client_order_id(stop_loss_client_order_id) + .reason(Ustr::from("Rejected")) + .build() + .unwrap(); + stop_loss_order + .apply(OrderEventAny::Rejected(rejected_event)) + .unwrap(); + + // Make take profit order Accepted + let accepted_take_profit = TestOrderStubs::make_accepted_order(&take_profit_order); + + // 1. save stop loss order in cache which is rejected and closed is set to true + // 2. send the take profit order which has linked the stop loss order + cache + .as_ref() + .borrow_mut() + .add_order(stop_loss_order.clone(), None, None, false) + .unwrap(); + let stop_loss_closed_after = stop_loss_order.is_closed(); + engine.process_order(&accepted_take_profit, account_id); + + // Get messages and test + let saved_messages = get_order_event_handler_messages(order_event_handler); + assert_eq!(saved_messages.len(), 1); + let first_message = saved_messages.first().unwrap(); + assert_eq!(first_message.event_type(), OrderEventType::Rejected); + assert_eq!( + first_message.message().unwrap(), + Ustr::from( + format!("Contingent order {stop_loss_client_order_id} already closed").as_str() + ) + ); + } } diff --git a/nautilus_core/execution/src/matching_core.rs b/nautilus_core/execution/src/matching_core.rs index 9e9dcc782b67..b998f0602a1a 100644 --- a/nautilus_core/execution/src/matching_core.rs +++ b/nautilus_core/execution/src/matching_core.rs @@ -486,6 +486,9 @@ mod tests { None, None, None, + None, + None, + None, ); let result = matching_core.is_stop_matched(&order.into()); @@ -523,6 +526,9 @@ mod tests { None, None, None, + None, + None, + None, ); matching_core.match_stop_order(&order.clone().into()); diff --git a/nautilus_core/model/src/orders/any.rs b/nautilus_core/model/src/orders/any.rs index f3fe97b8a74c..00d35f950bdf 100644 --- a/nautilus_core/model/src/orders/any.rs +++ b/nautilus_core/model/src/orders/any.rs @@ -575,6 +575,21 @@ impl OrderAny { Self::TrailingStopMarket(order) => order.contingency_type, } } + + #[must_use] + pub fn linked_order_ids(&self) -> Option> { + match self { + Self::Limit(order) => order.linked_order_ids.clone(), + Self::LimitIfTouched(order) => order.linked_order_ids.clone(), + Self::Market(order) => order.linked_order_ids.clone(), + Self::MarketIfTouched(order) => order.linked_order_ids.clone(), + Self::MarketToLimit(order) => order.linked_order_ids.clone(), + Self::StopLimit(order) => order.linked_order_ids.clone(), + Self::StopMarket(order) => order.linked_order_ids.clone(), + Self::TrailingStopLimit(order) => order.linked_order_ids.clone(), + Self::TrailingStopMarket(order) => order.linked_order_ids.clone(), + } + } } impl PartialEq for OrderAny { diff --git a/nautilus_core/model/src/orders/stubs.rs b/nautilus_core/model/src/orders/stubs.rs index dfdf4b6469a2..0b9fd5ce292a 100644 --- a/nautilus_core/model/src/orders/stubs.rs +++ b/nautilus_core/model/src/orders/stubs.rs @@ -19,14 +19,14 @@ use nautilus_core::{nanos::UnixNanos, uuid::UUID4}; use super::{any::OrderAny, limit::LimitOrder, stop_market::StopMarketOrder}; use crate::{ - enums::{LiquiditySide, OrderSide, TimeInForce, TriggerType}, + enums::{ContingencyType, LiquiditySide, OrderSide, TimeInForce, TriggerType}, events::order::{OrderAccepted, OrderEventAny, OrderFilled, OrderSubmitted}, identifiers::{ AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId, VenueOrderId, }, instruments::any::InstrumentAny, - orders::market::MarketOrder, + orders::{market::MarketOrder, market_if_touched::MarketIfTouchedOrder}, types::{money::Money, price::Price, quantity::Quantity}, }; @@ -235,14 +235,18 @@ impl TestOrderStubs { } #[must_use] + #[allow(clippy::too_many_arguments)] pub fn stop_market_order( instrument_id: InstrumentId, order_side: OrderSide, trigger_price: Price, quantity: Quantity, trigger_type: Option, + contingency_type: Option, client_order_id: Option, time_in_force: Option, + parent_order_id: Option, + linked_order_ids: Option>, ) -> OrderAny { let order = StopMarketOrder::new( TraderId::default(), @@ -260,10 +264,10 @@ impl TestOrderStubs { None, None, None, + contingency_type, None, - None, - None, - None, + linked_order_ids, + parent_order_id, None, None, None, @@ -275,6 +279,51 @@ impl TestOrderStubs { OrderAny::StopMarket(order) } + #[must_use] + #[allow(clippy::too_many_arguments)] + pub fn market_if_touched_order( + instrument_id: InstrumentId, + order_side: OrderSide, + trigger_price: Price, + quantity: Quantity, + trigger_type: Option, + contingency_type: Option, + client_order_id: Option, + time_in_force: Option, + linked_order_ids: Option>, + ) -> OrderAny { + OrderAny::MarketIfTouched( + MarketIfTouchedOrder::new( + TraderId::default(), + StrategyId::default(), + instrument_id, + client_order_id.unwrap_or_default(), + order_side, + quantity, + trigger_price, + trigger_type.unwrap_or(TriggerType::BidAsk), + time_in_force.unwrap_or(TimeInForce::Gtc), + None, + false, + false, + None, + None, + None, + contingency_type, + None, + linked_order_ids, + None, + None, + None, + None, + None, + UUID4::new(), + UnixNanos::default(), + ) + .unwrap(), + ) + } + pub fn make_accepted_order(order: &OrderAny) -> OrderAny { let mut new_order = order.clone(); let submitted_event = From d2605c59dc4ec2a93c5da5d00434138df88cf27c Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 12 Aug 2024 08:06:39 +1000 Subject: [PATCH 27/72] Update dependencies --- nautilus_core/Cargo.lock | 88 ++++++++++++++++++++-------------------- nautilus_core/Cargo.toml | 4 +- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index a4cf1571f451..e0b3063d29fc 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -418,7 +418,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -429,7 +429,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -611,7 +611,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "syn_derive", ] @@ -733,9 +733,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" +checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" dependencies = [ "jobserver", "libc", @@ -862,7 +862,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -974,9 +974,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" @@ -1139,7 +1139,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1150,7 +1150,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1516,7 +1516,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1558,7 +1558,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1568,7 +1568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1840,7 +1840,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3026,7 +3026,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3103,7 +3103,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3326,7 +3326,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3588,7 +3588,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3601,7 +3601,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3957,7 +3957,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.72", + "syn 2.0.74", "unicode-ident", ] @@ -4162,29 +4162,29 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.205" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" +checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.205" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ "itoa", "memchr", @@ -4397,7 +4397,7 @@ checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4462,7 +4462,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4485,7 +4485,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.72", + "syn 2.0.74", "tempfile", "tokio", "url", @@ -4642,7 +4642,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4664,9 +4664,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", @@ -4682,7 +4682,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4812,7 +4812,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4935,7 +4935,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -5096,7 +5096,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -5156,7 +5156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -5213,7 +5213,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -5400,7 +5400,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "wasm-bindgen-shared", ] @@ -5434,7 +5434,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5557,7 +5557,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -5568,7 +5568,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -5789,7 +5789,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] diff --git a/nautilus_core/Cargo.toml b/nautilus_core/Cargo.toml index d4796075e357..8ccf4e08b7a5 100644 --- a/nautilus_core/Cargo.toml +++ b/nautilus_core/Cargo.toml @@ -43,8 +43,8 @@ rmp-serde = "1.3.0" rust_decimal = "1.35.0" rust_decimal_macros = "1.35.0" semver = "1.0.23" -serde = { version = "1.0.205", features = ["derive"] } -serde_json = "1.0.122" +serde = { version = "1.0.206", features = ["derive"] } +serde_json = "1.0.124" strum = { version = "0.26.3", features = ["derive"] } thiserror = "1.0.63" thousands = "0.2.0" From 2628f3e84e7c7322da1f986116a940c0cf3b586f Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 12 Aug 2024 08:18:14 +1000 Subject: [PATCH 28/72] Continue DataEngine in Rust --- nautilus_core/data/src/engine/mod.rs | 42 ++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/nautilus_core/data/src/engine/mod.rs b/nautilus_core/data/src/engine/mod.rs index a20b8331e791..7fb394b46475 100644 --- a/nautilus_core/data/src/engine/mod.rs +++ b/nautilus_core/data/src/engine/mod.rs @@ -189,6 +189,34 @@ impl DataEngine { subs } + fn get_client(&self, client_id: &ClientId, venue: &Venue) -> Option<&DataClientAdapter> { + match self.clients.get(client_id) { + Some(client) => Some(client), + None => self + .routing_map + .get(venue) + .and_then(|client_id: &ClientId| self.clients.get(client_id)), + } + } + + fn get_client_mut( + &mut self, + client_id: &ClientId, + venue: &Venue, + ) -> Option<&mut DataClientAdapter> { + // Try to get client directly from clients map + if self.clients.contains_key(client_id) { + return self.clients.get_mut(client_id); + } + + // If not found, try to get client_id from routing map + if let Some(mapped_client_id) = self.routing_map.get(venue) { + return self.clients.get_mut(mapped_client_id); + } + + None + } + #[must_use] pub fn subscribed_custom_data(&self) -> Vec { self.collect_subscriptions(|client| &client.subscriptions_generic) @@ -264,16 +292,6 @@ impl DataEngine { log::info!("Deregistered client {client_id}"); } - fn get_client(&self, client_id: &ClientId, venue: &Venue) -> Option<&DataClientAdapter> { - match self.clients.get(client_id) { - Some(client) => Some(client), - None => self - .routing_map - .get(venue) - .and_then(|client_id: &ClientId| self.clients.get(client_id)), - } - } - pub fn execute(&mut self, msg: &dyn Any) { if let Some(cmd) = msg.downcast_ref::() { match cmd.data_type.type_name() { @@ -289,7 +307,7 @@ impl DataEngine { type_name => log::error!("Cannot handle request, type {type_name} is unrecognized"), } - if let Some(client) = self.clients.get_mut(&cmd.client_id) { + if let Some(client) = self.get_client_mut(&cmd.client_id, &cmd.venue) { client.execute(cmd.clone()); } else { log::error!( @@ -304,7 +322,7 @@ impl DataEngine { /// Send a [`DataRequest`] to an endpoint that must be a data client implementation. pub fn request(&self, req: DataRequest) { - if let Some(client) = self.clients.get(&req.client_id) { + if let Some(client) = self.get_client(&req.client_id, &req.venue) { client.through_request(req); } else { log::error!( From 48a7b7b790bed51ca160d1eae803a18fda148d25 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 12 Aug 2024 08:22:36 +1000 Subject: [PATCH 29/72] Fix clippy lints --- nautilus_core/backtest/src/matching_engine.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index dda6106a3fc1..3cd7dd0b851f 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -313,12 +313,12 @@ impl OrderMatchingEngine { { self.generate_order_rejected( order, - format!("Contingent order {} already closed", client_order_id) + format!("Contingent order {client_order_id} already closed") .into(), ); return; } - None => panic!("Cannot find contingent order for {}", client_order_id), + None => panic!("Cannot find contingent order for {client_order_id}"), _ => {} } } From a8119e60d1e946e9ba3d5c4b555f0bb257599bb7 Mon Sep 17 00:00:00 2001 From: Filip Macek Date: Mon, 12 Aug 2024 09:59:37 +0200 Subject: [PATCH 30/72] Implement process_market_order in OrderMatchingEngine (#1844) --- nautilus_core/backtest/src/matching_engine.rs | 136 +++++++++++++++++- nautilus_core/execution/src/matching_core.rs | 4 + 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index 3cd7dd0b851f..fb4ce56d83c1 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -448,7 +448,21 @@ impl OrderMatchingEngine { } fn process_market_order(&mut self, order: &OrderAny) { - todo!("process_market_order") + // Check if market exists + let order_side = order.order_side(); + let is_ask_initialized = self.core.is_ask_initialized; + let is_bid_initialized = self.core.is_bid_initialized; + if (order.order_side() == OrderSide::Buy && !self.core.is_ask_initialized) + || (order.order_side() == OrderSide::Sell && !self.core.is_bid_initialized) + { + self.generate_order_rejected( + order, + format!("No market for {}", order.instrument_id()).into(), + ); + return; + } + + self.fill_market_order(order); } fn process_limit_order(&mut self, order: &OrderAny) { @@ -538,8 +552,43 @@ impl OrderMatchingEngine { self.target_last = None; } - fn expire_order(&mut self, order: &PassiveOrderAny) { - todo!(); + fn determine_limit_price_and_volume(&self, order: &OrderAny) { + todo!("determine_limit_price_and_volume") + } + + fn determine_market_price_and_volume(&self, order: &OrderAny) { + todo!("determine_market_price_and_volume") + } + + fn fill_market_order(&mut self, order: &OrderAny) { + todo!("fill_market_order") + } + + fn fill_limit_order(&mut self, order: &OrderAny) { + todo!("fill_limit_order") + } + + fn apply_fills( + &mut self, + order: &OrderAny, + fills: Vec<(Price, Quantity)>, + liquidity_side: LiquiditySide, + venue_position_id: Option, + position: Option, + ) { + todo!("apply_fills") + } + + fn fill_order( + &mut self, + order: &OrderAny, + price: Price, + quantity: Quantity, + liquidity_side: LiquiditySide, + venue_position_id: Option, + position: Option, + ) { + todo!("fill_order") } fn update_trailing_stop_market(&mut self, order: &TrailingStopMarketOrder) { @@ -562,6 +611,36 @@ impl OrderMatchingEngine { TradeId::from(trade_id.as_str()) } + // -- EVENT HANDLING ----------------------------------------------------- + + fn accept_order(&mut self, order: &OrderAny) { + todo!("accept_order") + } + + fn expire_order(&mut self, order: &PassiveOrderAny) { + todo!("expire_order") + } + + fn cancel_order(&mut self, order: &OrderAny) { + todo!("cancel_order") + } + + fn update_order(&mut self, order: &OrderAny) { + todo!("update_order") + } + + fn trigger_stop_order(&mut self, order: &OrderAny) { + todo!("trigger_stop_order") + } + + fn update_contingent_order(&mut self, order: &OrderAny) { + todo!("update_contingent_order") + } + + fn cancel_contingent_orders(&mut self, order: &OrderAny) { + todo!("cancel_contingent_orders") + } + // -- EVENT GENERATORS ----------------------------------------------------- fn generate_order_rejected(&self, order: &OrderAny, reason: Ustr) { @@ -1429,4 +1508,55 @@ mod tests { ) ); } + + #[rstest] + fn test_process_market_order_no_market_rejected( + mut msgbus: MessageBus, + order_event_handler: ShareableMessageHandler, + account_id: AccountId, + time: AtomicTime, + instrument_es: InstrumentAny, + ) { + // Register saving message handler to exec engine endpoint + msgbus.register( + msgbus.switchboard.exec_engine_process, + order_event_handler.clone(), + ); + + // Create engine and process order + let mut engine = + get_order_matching_engine(instrument_es.clone(), Rc::new(msgbus), None, None, None); + let market_order_buy = TestOrderStubs::market_order( + instrument_es.id(), + OrderSide::Buy, + Quantity::from("1"), + None, + None, + ); + let market_order_sell = TestOrderStubs::market_order( + instrument_es.id(), + OrderSide::Sell, + Quantity::from("1"), + None, + None, + ); + engine.process_order(&market_order_buy, account_id); + engine.process_order(&market_order_sell, account_id); + + // Get messages and test + let saved_messages = get_order_event_handler_messages(order_event_handler); + assert_eq!(saved_messages.len(), 2); + let first = saved_messages.first().unwrap(); + let second = saved_messages.get(1).unwrap(); + assert_eq!(first.event_type(), OrderEventType::Rejected); + assert_eq!(second.event_type(), OrderEventType::Rejected); + assert_eq!( + first.message().unwrap(), + Ustr::from("No market for ESZ1.GLBX") + ); + assert_eq!( + second.message().unwrap(), + Ustr::from("No market for ESZ1.GLBX") + ); + } } diff --git a/nautilus_core/execution/src/matching_core.rs b/nautilus_core/execution/src/matching_core.rs index b998f0602a1a..43018f5251be 100644 --- a/nautilus_core/execution/src/matching_core.rs +++ b/nautilus_core/execution/src/matching_core.rs @@ -42,6 +42,8 @@ pub struct OrderMatchingCore { pub ask: Option, /// The last price for the matching core. pub last: Option, + pub is_bid_initialized: bool, + pub is_ask_initialized: bool, orders_bid: Vec, orders_ask: Vec, trigger_stop_order: Option, @@ -65,6 +67,8 @@ impl OrderMatchingCore { bid: None, ask: None, last: None, + is_bid_initialized: false, + is_ask_initialized: false, orders_bid: Vec::new(), orders_ask: Vec::new(), trigger_stop_order, From 069e3b0e1f98cac9abec4181aecc5f77f7786754 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 12 Aug 2024 19:13:36 +1000 Subject: [PATCH 31/72] Fix Position exception type on duplicate fill --- RELEASES.md | 1 + nautilus_trader/model/position.pyx | 2 +- tests/unit_tests/model/test_position.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index ee83599fab39..1bd9e4eff195 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -12,6 +12,7 @@ Released on TBD (UTC). - Changed `VolumeWeightedAveragePrice` calculation formula to use each bars "typical" price (#1842), thanks @evgenii-prusov ### Fixes +- Fixed `Position` exception type on duplicate fill (should be `KeyError` like `Order`) - Fixed Bybit position report parsing when position is flat (`BybitPositionSide` now correctly handles the empty string) --- diff --git a/nautilus_trader/model/position.pyx b/nautilus_trader/model/position.pyx index 804801a7397f..529106bcbd27 100644 --- a/nautilus_trader/model/position.pyx +++ b/nautilus_trader/model/position.pyx @@ -636,7 +636,7 @@ cdef class Position: and fill.last_px == p_fill.last_px and fill.last_qty == p_fill.last_qty ): - raise ValueError(f"Duplicate {fill.trade_id!r} in events {fill} {p_fill}") + raise KeyError(f"Duplicate {fill.trade_id!r} in events {fill} {p_fill}") cdef void _handle_buy_order_fill(self, OrderFilled fill): # Initialize realized PnL for fill diff --git a/tests/unit_tests/model/test_position.py b/tests/unit_tests/model/test_position.py index 93dc39f31ce4..2953cfe8a868 100644 --- a/tests/unit_tests/model/test_position.py +++ b/tests/unit_tests/model/test_position.py @@ -355,7 +355,7 @@ def test_position_filled_with_duplicate_trade_id_and_same_trade(self) -> None: position = Position(instrument=AUDUSD_SIM, fill=fill) # Act - with pytest.raises(ValueError): + with pytest.raises(KeyError): position.apply(fill) def test_position_filled_with_buy_order(self) -> None: From 04e2c31f63b231ddaf60ddc5aa06e7a4f253809f Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 12 Aug 2024 20:36:05 +1000 Subject: [PATCH 32/72] Add analysis crate --- nautilus_core/Cargo.lock | 20 +++++++++++++ nautilus_core/Cargo.toml | 1 + nautilus_core/analysis/Cargo.toml | 49 +++++++++++++++++++++++++++++++ nautilus_core/analysis/src/lib.rs | 32 ++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 nautilus_core/analysis/Cargo.toml create mode 100644 nautilus_core/analysis/src/lib.rs diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index e0b3063d29fc..c30dd061acb8 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -2592,6 +2592,26 @@ dependencies = [ "ustr", ] +[[package]] +name = "nautilus-analysis" +version = "0.29.0" +dependencies = [ + "anyhow", + "chrono", + "criterion", + "indexmap 2.3.0", + "nautilus-common", + "nautilus-core", + "nautilus-model", + "pyo3", + "rstest", + "rust_decimal", + "rust_decimal_macros", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "nautilus-backtest" version = "0.29.0" diff --git a/nautilus_core/Cargo.toml b/nautilus_core/Cargo.toml index 8ccf4e08b7a5..e2b785fad5a3 100644 --- a/nautilus_core/Cargo.toml +++ b/nautilus_core/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "adapters", + "analysis", "backtest", "common", "core", diff --git a/nautilus_core/analysis/Cargo.toml b/nautilus_core/analysis/Cargo.toml new file mode 100644 index 000000000000..35d5455e7a43 --- /dev/null +++ b/nautilus_core/analysis/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "nautilus-analysis" +version.workspace = true +edition.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true + +[lib] +name = "nautilus_analysis" +crate-type = ["rlib", "staticlib", "cdylib"] + +[dependencies] +nautilus-common = { path = "../common" } +nautilus-core = { path = "../core" } +nautilus-model = { path = "../model", features = ["stubs"] } +anyhow = { workspace = true } +chrono = { workspace = true } +indexmap = { workspace = true } +pyo3 = { workspace = true, optional = true } +rust_decimal = { workspace = true } +rust_decimal_macros = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +criterion = { workspace = true } +rstest = { workspace = true } + +[features] +default = ["ffi", "python"] +extension-module = [ + "pyo3/extension-module", + "nautilus-common/extension-module", + "nautilus-core/extension-module", + "nautilus-model/extension-module", +] +ffi = [ + "nautilus-common/ffi", + "nautilus-core/ffi", + "nautilus-model/ffi", +] +python = [ + "pyo3", + "nautilus-common/python", + "nautilus-core/python", + "nautilus-model/python", +] diff --git a/nautilus_core/analysis/src/lib.rs b/nautilus_core/analysis/src/lib.rs new file mode 100644 index 000000000000..e159f7acd2f7 --- /dev/null +++ b/nautilus_core/analysis/src/lib.rs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2024 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +//! [NautilusTrader](http://nautilustrader.io) is an open-source, high-performance, production-grade +//! algorithmic trading platform, providing quantitative traders with the ability to backtest +//! portfolios of automated trading strategies on historical data with an event-driven engine, +//! and also deploy those same strategies live, with no code changes. +//! +//! +//! The platform is modularly designed to work with *adapters*, enabling connectivity to trading venues +//! and data providers by converting their raw APIs into a unified interface. +//! +//! # Feature flags +//! +//! This crate provides feature flags to control source code inclusion during compilation, +//! depending on the intended use case, i.e. whether to provide Python bindings +//! for the main `nautilus_trader` Python package, or as part of a Rust only build. +//! +//! - `ffi`: Enables the C foreign function interface (FFI) from `cbindgen` +//! - `python`: Enables Python bindings from `pyo3` From 535107cf80c4f82c9acb059263adb4827c07c247 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 12 Aug 2024 21:41:45 +1000 Subject: [PATCH 33/72] Upgrade datafusion crate --- RELEASES.md | 11 +-- nautilus_core/Cargo.lock | 104 +++++++++++++++------------ nautilus_core/persistence/Cargo.toml | 2 +- poetry.lock | 14 ++++ 4 files changed, 81 insertions(+), 50 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 1bd9e4eff195..66fb8747fe33 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -7,6 +7,7 @@ Released on TBD (UTC). - Improve `BinanceExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improve `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improve `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions +- Upgraded `datafusion` crate to v41.0.0 ### Breaking Changes - Changed `VolumeWeightedAveragePrice` calculation formula to use each bars "typical" price (#1842), thanks @evgenii-prusov @@ -25,7 +26,7 @@ Released on 9th August 2024 (UTC). - Added `@customdata` decorator to reduce need for boiler plate implementing custom data types (#1828), thanks @faysou - Added timeout for HTTP client in Rust (#1835), thanks @davidsblom - Added catalog conversion function of streamed data to backtest data (#1834), thanks @faysou -- Upgraded Cython to 3.0.11 +- Upgraded Cython to v3.0.11 ### Breaking Changes None @@ -57,8 +58,8 @@ Released on 2nd August 2024 (UTC). - Refactored order submission error handling for Interactive Brokers (#1783), thanks @rsmb7z - Improved live reconciliation robustness (will now generate inferred orders necessary to align external position state) - Improved tests for Interactive Brokers (#1776), thanks @mylesgamez -- Upgraded `tokio` crate to 1.39.2 -- Upgraded `datafusion` crate to 40.0.0 +- Upgraded `tokio` crate to v1.39.2 +- Upgraded `datafusion` crate to v40.0.0 ### Breaking Changes - Removed `VenueStatus` and all associated methods and schemas (redundant with `InstrumentStatus`) @@ -308,7 +309,7 @@ Released on 22nd March 2024 (UTC). - Ported `VIDYA` indicator to Rust, thanks @Pushkarm029 - Refactored `InteractiveBrokersEWrapper`, thanks @rsmb7z - Redact Redis passwords in strings and logs -- Upgraded `redis` crate to 0.25.2 which bumps up TLS dependencies, and turned on `tls-rustls-webpki-roots` feature flag +- Upgraded `redis` crate to v0.25.2 which bumps up TLS dependencies, and turned on `tls-rustls-webpki-roots` feature flag ### Breaking Changes None @@ -784,7 +785,7 @@ Released on 31st July 2023 (UTC). - Added `USDP` (Pax Dollar) and `TUSD` (TrueUSD) stablecoins - Improved `OrderMatchingEngine` handling when no fills (an error is now logged) - Improved `Binance` live clients logging -- Upgraded Cython to 3.0.0 stable +- Upgraded Cython to v3.0.0 stable ### Breaking Changes - Moved `filter_unclaimed_external_orders` from `ExecEngineConfig` to `LiveExecEngineConfig` diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index c30dd061acb8..ede87dcd0084 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -1153,19 +1153,6 @@ dependencies = [ "syn 2.0.74", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "dashmap" version = "6.0.1" @@ -1209,9 +1196,9 @@ dependencies = [ [[package]] name = "datafusion" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9d55a9cd2634818953809f75ebe5248b00dd43c3227efb2a51a2d5feaad54e" +checksum = "e4fd4a99fc70d40ef7e52b243b4a399c3f8d353a40d5ecb200deee05e49c61bb" dependencies = [ "ahash 0.8.11", "arrow", @@ -1223,7 +1210,8 @@ dependencies = [ "bytes", "bzip2", "chrono", - "dashmap 5.5.3", + "dashmap", + "datafusion-catalog", "datafusion-common", "datafusion-common-runtime", "datafusion-execution", @@ -1233,6 +1221,7 @@ dependencies = [ "datafusion-optimizer", "datafusion-physical-expr", "datafusion-physical-expr-common", + "datafusion-physical-optimizer", "datafusion-physical-plan", "datafusion-sql", "flate2", @@ -1260,11 +1249,25 @@ dependencies = [ "zstd", ] +[[package]] +name = "datafusion-catalog" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b3cfbd84c6003594ae1972314e3df303a27ce8ce755fcea3240c90f4c0529" +dependencies = [ + "arrow-schema", + "async-trait", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-plan", +] + [[package]] name = "datafusion-common" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def66b642959e7f96f5d2da22e1f43d3bd35598f821e5ce351a0553e0f1b7367" +checksum = "44fdbc877e3e40dcf88cc8f283d9f5c8851f0a3aa07fee657b1b75ac1ad49b9c" dependencies = [ "ahash 0.8.11", "arrow", @@ -1285,22 +1288,22 @@ dependencies = [ [[package]] name = "datafusion-common-runtime" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f104bb9cb44c06c9badf8a0d7e0855e5f7fa5e395b887d7f835e8a9457dc1352" +checksum = "8a7496d1f664179f6ce3a5cbef6566056ccaf3ea4aa72cc455f80e62c1dd86b1" dependencies = [ "tokio", ] [[package]] name = "datafusion-execution" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac0fd8b5d80bbca3fc3b6f40da4e9f6907354824ec3b18bbd83fee8cf5c3c3e" +checksum = "799e70968c815b611116951e3dd876aef04bf217da31b72eec01ee6a959336a1" dependencies = [ "arrow", "chrono", - "dashmap 5.5.3", + "dashmap", "datafusion-common", "datafusion-expr", "futures", @@ -1315,9 +1318,9 @@ dependencies = [ [[package]] name = "datafusion-expr" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2103d2cc16fb11ef1fa993a6cac57ed5cb028601db4b97566c90e5fa77aa1e68" +checksum = "1c1841c409d9518c17971d15c9bae62e629eb937e6fb6c68cd32e9186f8b30d2" dependencies = [ "ahash 0.8.11", "arrow", @@ -1334,11 +1337,12 @@ dependencies = [ [[package]] name = "datafusion-functions" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a369332afd0ef5bd565f6db2139fb9f1dfdd0afa75a7f70f000b74208d76994f" +checksum = "a8e481cf34d2a444bd8fa09b65945f0ce83dc92df8665b761505b3d9f351bebb" dependencies = [ "arrow", + "arrow-buffer", "base64", "chrono", "datafusion-common", @@ -1356,9 +1360,9 @@ dependencies = [ [[package]] name = "datafusion-functions-aggregate" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92718db1aff70c47e5abf9fc975768530097059e5db7c7b78cd64b5e9a11fc77" +checksum = "2b4ece19f73c02727e5e8654d79cd5652de371352c1df3c4ac3e419ecd6943fb" dependencies = [ "ahash 0.8.11", "arrow", @@ -1374,9 +1378,9 @@ dependencies = [ [[package]] name = "datafusion-optimizer" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f34692011bec4fdd6fc18c264bf8037b8625d801e6dd8f5111af15cb6d71d3" +checksum = "791ff56f55608bc542d1ea7a68a64bdc86a9413f5a381d06a39fd49c2a3ab906" dependencies = [ "arrow", "async-trait", @@ -1394,9 +1398,9 @@ dependencies = [ [[package]] name = "datafusion-physical-expr" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45538630defedb553771434a437f7ca8f04b9b3e834344aafacecb27dc65d5e5" +checksum = "9a223962b3041304a3e20ed07a21d5de3d88d7e4e71ca192135db6d24e3365a4" dependencies = [ "ahash 0.8.11", "arrow", @@ -1424,9 +1428,9 @@ dependencies = [ [[package]] name = "datafusion-physical-expr-common" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d8a72b0ca908e074aaeca52c14ddf5c28d22361e9cb6bc79bb733cd6661b536" +checksum = "db5e7d8532a1601cd916881db87a70b0a599900d23f3db2897d389032da53bc6" dependencies = [ "ahash 0.8.11", "arrow", @@ -1436,11 +1440,23 @@ dependencies = [ "rand", ] +[[package]] +name = "datafusion-physical-optimizer" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb9c78f308e050f5004671039786a925c3fee83b90004e9fcfd328d7febdcc0" +dependencies = [ + "datafusion-common", + "datafusion-execution", + "datafusion-physical-expr", + "datafusion-physical-plan", +] + [[package]] name = "datafusion-physical-plan" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b504eae6107a342775e22e323e9103f7f42db593ec6103b28605b7b7b1405c4a" +checksum = "8d1116949432eb2d30f6362707e2846d942e491052a206f2ddcb42d08aea1ffe" dependencies = [ "ahash 0.8.11", "arrow", @@ -1472,9 +1488,9 @@ dependencies = [ [[package]] name = "datafusion-sql" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5db33f323f41b95ae201318ba654a9bf11113e58a51a1dff977b1a836d3d889" +checksum = "b45d0180711165fe94015d7c4123eb3e1cf5fb60b1506453200b8d1ce666bef0" dependencies = [ "arrow", "arrow-array", @@ -2535,9 +2551,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", @@ -2829,7 +2845,7 @@ dependencies = [ "axum", "bytes", "criterion", - "dashmap 6.0.1", + "dashmap", "futures", "futures-util", "http", @@ -4401,9 +4417,9 @@ dependencies = [ [[package]] name = "sqlparser" -version = "0.47.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295e9930cd7a97e58ca2a070541a3ca502b17f5d1fa7157376d0fabd85324f25" +checksum = "a4a404d0e14905361b918cb8afdb73605e25c1d5029312bd9785142dcb3aa49e" dependencies = [ "log", "sqlparser_derive", diff --git a/nautilus_core/persistence/Cargo.toml b/nautilus_core/persistence/Cargo.toml index 7edc03b0c5f9..9f787c0868d5 100644 --- a/nautilus_core/persistence/Cargo.toml +++ b/nautilus_core/persistence/Cargo.toml @@ -22,7 +22,7 @@ tokio = { workspace = true } thiserror = { workspace = true } binary-heap-plus = "0.5.0" compare = "0.1.0" -datafusion = { version = "40.0.0", default-features = false, features = ["compression", "regex_expressions", "unicode_expressions", "pyarrow"] } +datafusion = { version = "41.0.0", default-features = false, features = ["compression", "regex_expressions", "unicode_expressions", "pyarrow"] } dotenv = "0.15.0" [dev-dependencies] diff --git a/poetry.lock b/poetry.lock index af75c7b1d33c..f178fbbeb7ce 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1354,6 +1354,7 @@ files = [ {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, @@ -1548,6 +1549,19 @@ files = [ {file = "pyarrow-17.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:392bc9feabc647338e6c89267635e111d71edad5fcffba204425a7c8d13610d7"}, {file = "pyarrow-17.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:af5ff82a04b2171415f1410cff7ebb79861afc5dae50be73ce06d6e870615204"}, {file = "pyarrow-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:edca18eaca89cd6382dfbcff3dd2d87633433043650c07375d095cd3517561d8"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c7916bff914ac5d4a8fe25b7a25e432ff921e72f6f2b7547d1e325c1ad9d155"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f553ca691b9e94b202ff741bdd40f6ccb70cdd5fbf65c187af132f1317de6145"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0cdb0e627c86c373205a2f94a510ac4376fdc523f8bb36beab2e7f204416163c"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:d7d192305d9d8bc9082d10f361fc70a73590a4c65cf31c3e6926cd72b76bc35c"}, + {file = "pyarrow-17.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:02dae06ce212d8b3244dd3e7d12d9c4d3046945a5933d28026598e9dbbda1fca"}, + {file = "pyarrow-17.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:13d7a460b412f31e4c0efa1148e1d29bdf18ad1411eb6757d38f8fbdcc8645fb"}, + {file = "pyarrow-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b564a51fbccfab5a04a80453e5ac6c9954a9c5ef2890d1bcf63741909c3f8df"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32503827abbc5aadedfa235f5ece8c4f8f8b0a3cf01066bc8d29de7539532687"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a155acc7f154b9ffcc85497509bcd0d43efb80d6f733b0dc3bb14e281f131c8b"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:dec8d129254d0188a49f8a1fc99e0560dc1b85f60af729f47de4046015f9b0a5"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:a48ddf5c3c6a6c505904545c25a4ae13646ae1f8ba703c4df4a1bfe4f4006bda"}, + {file = "pyarrow-17.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:42bf93249a083aca230ba7e2786c5f673507fa97bbd9725a1e2754715151a204"}, + {file = "pyarrow-17.0.0.tar.gz", hash = "sha256:4beca9521ed2c0921c1023e68d097d0299b62c362639ea315572a58f3f50fd28"}, ] [package.dependencies] From 94dc343b8b030eaad4ebd5471a36f7a25b5ab4eb Mon Sep 17 00:00:00 2001 From: Filip Macek Date: Tue, 13 Aug 2024 10:57:03 +0200 Subject: [PATCH 34/72] Bootstrap SimulatedExchange in Rust (#1847) --- nautilus_core/backtest/src/exchange.rs | 278 ++++++++++++++++++ nautilus_core/backtest/src/lib.rs | 1 + nautilus_core/backtest/src/matching_engine.rs | 151 ++++++---- nautilus_core/backtest/src/models/fee.rs | 6 + nautilus_core/backtest/src/models/latency.rs | 16 + nautilus_core/backtest/src/models/mod.rs | 1 + nautilus_core/backtest/src/modules/mod.rs | 29 ++ 7 files changed, 426 insertions(+), 56 deletions(-) create mode 100644 nautilus_core/backtest/src/models/latency.rs create mode 100644 nautilus_core/backtest/src/modules/mod.rs diff --git a/nautilus_core/backtest/src/exchange.rs b/nautilus_core/backtest/src/exchange.rs index 97d459d8d1e8..8c403c7f5c35 100644 --- a/nautilus_core/backtest/src/exchange.rs +++ b/nautilus_core/backtest/src/exchange.rs @@ -12,3 +12,281 @@ // See the License for the specific language governing permissions and // limitations under the License. // ------------------------------------------------------------------------------------------------- + +// Under development +#![allow(dead_code)] +#![allow(unused_variables)] + +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use nautilus_common::{cache::Cache, msgbus::MessageBus}; +use nautilus_core::{correctness::check_equal, nanos::UnixNanos, time::AtomicTime}; +use nautilus_execution::{client::ExecutionClient, messages::TradingCommand}; +use nautilus_model::{ + data::{ + bar::Bar, delta::OrderBookDelta, deltas::OrderBookDeltas, quote::QuoteTick, + status::InstrumentStatus, trade::TradeTick, + }, + enums::{AccountType, BookType, OmsType}, + identifiers::{InstrumentId, Venue}, + instruments::any::InstrumentAny, + types::{currency::Currency, money::Money}, +}; +use rust_decimal::Decimal; + +use crate::{ + matching_engine::{OrderMatchingEngine, OrderMatchingEngineConfig}, + models::{fee::FeeModelAny, fill::FillModel, latency::LatencyModel}, + modules::SimulationModule, +}; + +pub struct SimulatedExchange { + id: Venue, + oms_type: OmsType, + account_type: AccountType, + book_type: BookType, + default_leverage: Decimal, + exec_client: Option, + fee_model: FeeModelAny, + fill_model: FillModel, + latency_model: LatencyModel, + instruments: HashMap, + matching_engines: HashMap, + leverages: HashMap, + modules: Vec>, + clock: &'static AtomicTime, + msgbus: Rc>, + cache: Rc>, + frozen_account: bool, + bar_execution: bool, + reject_stop_orders: bool, + support_gtd_orders: bool, + support_contingent_orders: bool, + use_position_ids: bool, + use_random_ids: bool, + use_reduce_only: bool, + use_message_queue: bool, +} + +impl SimulatedExchange { + #[allow(clippy::too_many_arguments)] + pub fn new( + venue: Venue, + oms_type: OmsType, + account_type: AccountType, + starting_balances: Vec, + base_currency: Option, + default_leverage: Decimal, + leverages: HashMap, + modules: Vec>, + msgbus: Rc>, // TODO add portfolio + cache: Rc>, + clock: &'static AtomicTime, + fill_model: FillModel, + fee_model: FeeModelAny, + latency_model: LatencyModel, + book_type: BookType, + frozen_account: Option, + bar_execution: Option, + reject_stop_orders: Option, + support_gtd_orders: Option, + support_contingent_orders: Option, + use_position_ids: Option, + use_random_ids: Option, + use_reduce_only: Option, + use_message_queue: Option, + ) -> anyhow::Result { + if starting_balances.is_empty() { + anyhow::bail!("Starting balances must be provided") + } + if base_currency.is_some() && starting_balances.len() > 1 { + anyhow::bail!("single-currency account has multiple starting currencies") + } + // TODO register and load modules + Ok(SimulatedExchange { + id: venue, + oms_type, + account_type, + book_type, + default_leverage, + exec_client: None, + fee_model, + fill_model, + latency_model, + instruments: HashMap::new(), + matching_engines: HashMap::new(), + leverages, + modules, + clock, + msgbus, + cache, + frozen_account: frozen_account.unwrap_or(false), + bar_execution: bar_execution.unwrap_or(true), + reject_stop_orders: reject_stop_orders.unwrap_or(true), + support_gtd_orders: support_gtd_orders.unwrap_or(true), + support_contingent_orders: support_contingent_orders.unwrap_or(true), + use_position_ids: use_position_ids.unwrap_or(true), + use_random_ids: use_random_ids.unwrap_or(false), + use_reduce_only: use_reduce_only.unwrap_or(true), + use_message_queue: use_message_queue.unwrap_or(true), + }) + } + + pub fn register_client(&mut self, client: ExecutionClient) { + let client_id = client.client_id; + self.exec_client = Some(client); + log::info!("Registered ExecutionClient: {client_id}"); + } + + pub fn set_fill_model(&mut self, _fill_model: FillModel) { + todo!("set fill model") + } + + pub fn set_latency_model(&mut self, _latency_model: LatencyModel) { + todo!("set latency model") + } + + pub fn initialize_account(&mut self, _account_id: u64) { + todo!("initialize account") + } + + pub fn add_instrument(&mut self, instrument: InstrumentAny) -> anyhow::Result<()> { + check_equal( + instrument.id().venue, + self.id, + "Venue of instrument id", + "Venue of simulated exchange", + ) + .unwrap(); + + if self.account_type == AccountType::Cash + && (matches!(instrument, InstrumentAny::CryptoPerpetual(_)) + || matches!(instrument, InstrumentAny::CryptoFuture(_))) + { + anyhow::bail!("Cash account cannot trade futures or perpetuals") + } + + self.instruments.insert(instrument.id(), instrument.clone()); + + let matching_engine_config = OrderMatchingEngineConfig::new( + self.bar_execution, + self.reject_stop_orders, + self.support_gtd_orders, + self.support_contingent_orders, + self.use_position_ids, + self.use_random_ids, + self.use_reduce_only, + ); + let instrument_id = instrument.id(); + let matching_engine = OrderMatchingEngine::new( + instrument, + self.instruments.len() as u32, + self.book_type, + self.oms_type, + self.account_type, + self.clock, + Rc::clone(&self.msgbus), + Rc::clone(&self.cache), + matching_engine_config, + ); + self.matching_engines.insert(instrument_id, matching_engine); + + log::info!( + "Added instrument {} and created matching engine", + instrument_id + ); + Ok(()) + } + + pub fn best_bid_price(&self, _instrument_id: InstrumentId) { + todo!("best bid price") + } + + pub fn best_ask_price(&self, _instrument_id: InstrumentId) { + todo!("best ask price") + } + + pub fn get_book(&self, _instrument_id: InstrumentId) { + todo!("best bid qty") + } + + pub fn get_matching_engine(&self, _instrument_id: InstrumentId) { + todo!("get matching engine") + } + + pub fn get_matching_engines(&self) { + todo!("get matching engines") + } + + pub fn get_books(&self) { + todo!("get books") + } + + pub fn get_open_orders(&self, _instrument_id: Option) { + todo!("get open orders") + } + + pub fn get_open_bid_orders(&self, _instrument_id: Option) { + todo!("get open bid orders") + } + + pub fn get_open_ask_orders(&self, _instrument_id: Option) { + todo!("get open ask orders") + } + + pub fn get_account(&self) { + todo!("get account") + } + + pub fn adjust_account(&mut self, _adjustment: Money) { + todo!("adjust account") + } + + pub fn send(&self, _command: TradingCommand) { + todo!("send") + } + + pub fn generate_inflight_command(&self, _command: TradingCommand) { + todo!("generate inflight command") + } + + pub fn process_order_book_delta(&mut self, _delta: OrderBookDelta) { + todo!("process order book delta") + } + + pub fn process_order_book_deltas(&mut self, _deltas: OrderBookDeltas) { + todo!("process order book deltas") + } + + pub fn process_quote_tick(&mut self, _tick: QuoteTick) { + todo!("process quote tick") + } + + pub fn process_trade_tick(&mut self, _tick: TradeTick) { + todo!("process trade tick") + } + + pub fn process_bar(&mut self, _bar: Bar) { + todo!("process bar") + } + + pub fn process_instrument_status(&mut self, _status: InstrumentStatus) { + todo!("process instrument status") + } + + pub fn process(&mut self, _ts_now: UnixNanos) { + todo!("process") + } + + pub fn reset(&mut self) { + todo!("reset") + } + + pub fn process_trading_command(&mut self, _command: TradingCommand) { + todo!("process trading command") + } + + pub fn generate_fresh_account_state(&self) { + todo!("generate fresh account state") + } +} diff --git a/nautilus_core/backtest/src/lib.rs b/nautilus_core/backtest/src/lib.rs index f47310d049e4..b2a54623c2ad 100644 --- a/nautilus_core/backtest/src/lib.rs +++ b/nautilus_core/backtest/src/lib.rs @@ -32,3 +32,4 @@ pub mod engine; pub mod exchange; pub mod matching_engine; pub mod models; +pub mod modules; diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index fb4ce56d83c1..21ade348f86e 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -65,6 +65,28 @@ pub struct OrderMatchingEngineConfig { pub use_reduce_only: bool, } +impl OrderMatchingEngineConfig { + pub fn new( + bar_execution: bool, + reject_stop_orders: bool, + support_gtd_orders: bool, + support_contingent_orders: bool, + use_position_ids: bool, + use_random_ids: bool, + use_reduce_only: bool, + ) -> Self { + Self { + support_gtd_orders, + bar_execution, + reject_stop_orders, + support_contingent_orders, + use_position_ids, + use_random_ids, + use_reduce_only, + } + } +} + #[allow(clippy::derivable_impls)] impl Default for OrderMatchingEngineConfig { fn default() -> Self { @@ -99,7 +121,7 @@ pub struct OrderMatchingEngine { /// The config for the matching engine. pub config: OrderMatchingEngineConfig, clock: &'static AtomicTime, - msgbus: Rc, + msgbus: Rc>, cache: Rc>, book: OrderBook, core: OrderMatchingCore, @@ -126,7 +148,7 @@ impl OrderMatchingEngine { oms_type: OmsType, account_type: AccountType, clock: &'static AtomicTime, - msgbus: Rc, + msgbus: Rc>, cache: Rc>, config: OrderMatchingEngineConfig, ) -> Self { @@ -664,10 +686,8 @@ impl OrderMatchingEngine { ) .unwrap(), ); - self.msgbus.send( - &self.msgbus.switchboard.exec_engine_process, - &event as &dyn Any, - ); + let msgbus = self.msgbus.as_ref().borrow(); + msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any); } fn generate_order_accepted(&self, order: &OrderAny, venue_order_id: VenueOrderId) { @@ -690,10 +710,8 @@ impl OrderMatchingEngine { ) .unwrap(), ); - self.msgbus.send( - &self.msgbus.switchboard.exec_engine_process, - &event as &dyn Any, - ); + let msgbus = self.msgbus.as_ref().borrow(); + msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any); } #[allow(clippy::too_many_arguments)] @@ -724,10 +742,8 @@ impl OrderMatchingEngine { ) .unwrap(), ); - self.msgbus.send( - &self.msgbus.switchboard.exec_engine_process, - &event as &dyn Any, - ); + let msgbus = self.msgbus.as_ref().borrow(); + msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any); } #[allow(clippy::too_many_arguments)] @@ -758,10 +774,8 @@ impl OrderMatchingEngine { ) .unwrap(), ); - self.msgbus.send( - &self.msgbus.switchboard.exec_engine_process, - &event as &dyn Any, - ); + let msgbus = self.msgbus.as_ref().borrow(); + msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any); } fn generate_order_updated( @@ -790,10 +804,8 @@ impl OrderMatchingEngine { ) .unwrap(), ); - self.msgbus.send( - &self.msgbus.switchboard.exec_engine_process, - &event as &dyn Any, - ); + let msgbus = self.msgbus.as_ref().borrow(); + msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any); } fn generate_order_canceled(&self, order: &OrderAny, venue_order_id: VenueOrderId) { @@ -813,10 +825,8 @@ impl OrderMatchingEngine { ) .unwrap(), ); - self.msgbus.send( - &self.msgbus.switchboard.exec_engine_process, - &event as &dyn Any, - ); + let msgbus = self.msgbus.as_ref().borrow(); + msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any); } fn generate_order_triggered(&self, order: &OrderAny) { @@ -836,10 +846,8 @@ impl OrderMatchingEngine { ) .unwrap(), ); - self.msgbus.send( - &self.msgbus.switchboard.exec_engine_process, - &event as &dyn Any, - ); + let msgbus = self.msgbus.as_ref().borrow(); + msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any); } fn generate_order_expired(&self, order: &OrderAny) { @@ -859,10 +867,8 @@ impl OrderMatchingEngine { ) .unwrap(), ); - self.msgbus.send( - &self.msgbus.switchboard.exec_engine_process, - &event as &dyn Any, - ); + let msgbus = self.msgbus.as_ref().borrow(); + msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any); } #[allow(clippy::too_many_arguments)] @@ -905,10 +911,8 @@ impl OrderMatchingEngine { ) .unwrap(), ); - self.msgbus.send( - &self.msgbus.switchboard.exec_engine_process, - &event as &dyn Any, - ); + let msgbus = self.msgbus.as_ref().borrow(); + msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any); } } @@ -1007,7 +1011,7 @@ mod tests { fn get_order_matching_engine( instrument: InstrumentAny, - msgbus: Rc, + msgbus: Rc>, cache: Option>>, account_type: Option, config: Option, @@ -1051,8 +1055,13 @@ mod tests { ); // Create engine and process order - let mut engine = - get_order_matching_engine(instrument.clone(), Rc::new(msgbus), None, None, None); + let mut engine = get_order_matching_engine( + instrument.clone(), + Rc::new(RefCell::new(msgbus)), + None, + None, + None, + ); let order = TestOrderStubs::market_order( instrument.id(), OrderSide::Buy, @@ -1102,8 +1111,13 @@ mod tests { ); // Create engine and process order - let mut engine = - get_order_matching_engine(instrument.clone(), Rc::new(msgbus), None, None, None); + let mut engine = get_order_matching_engine( + instrument.clone(), + Rc::new(RefCell::new(msgbus)), + None, + None, + None, + ); let order = TestOrderStubs::market_order( instrument.id(), OrderSide::Buy, @@ -1139,8 +1153,13 @@ mod tests { ); // Create engine and process order - let mut engine = - get_order_matching_engine(instrument_es.clone(), Rc::new(msgbus), None, None, None); + let mut engine = get_order_matching_engine( + instrument_es.clone(), + Rc::new(RefCell::new(msgbus)), + None, + None, + None, + ); let order = TestOrderStubs::market_order( instrument_es.id(), OrderSide::Buy, @@ -1176,8 +1195,13 @@ mod tests { ); // Create engine and process order - let mut engine = - get_order_matching_engine(instrument_es.clone(), Rc::new(msgbus), None, None, None); + let mut engine = get_order_matching_engine( + instrument_es.clone(), + Rc::new(RefCell::new(msgbus)), + None, + None, + None, + ); let limit_order = TestOrderStubs::limit_order( instrument_es.id(), OrderSide::Sell, @@ -1215,8 +1239,13 @@ mod tests { ); // Create engine and process order - let mut engine = - get_order_matching_engine(instrument_es.clone(), Rc::new(msgbus), None, None, None); + let mut engine = get_order_matching_engine( + instrument_es.clone(), + Rc::new(RefCell::new(msgbus)), + None, + None, + None, + ); let stop_order = TestOrderStubs::stop_market_order( instrument_es.id(), OrderSide::Sell, @@ -1259,8 +1288,13 @@ mod tests { ); // Create engine and process order - let mut engine = - get_order_matching_engine(instrument.clone(), Rc::new(msgbus), None, None, None); + let mut engine = get_order_matching_engine( + instrument.clone(), + Rc::new(RefCell::new(msgbus)), + None, + None, + None, + ); let order = TestOrderStubs::market_order( instrument.id(), OrderSide::Sell, @@ -1303,7 +1337,7 @@ mod tests { let mut engine = get_order_matching_engine( instrument_es.clone(), - Rc::new(msgbus), + Rc::new(RefCell::new(msgbus)), None, None, Some(engine_config), @@ -1347,7 +1381,7 @@ mod tests { let cache = Rc::new(RefCell::new(Cache::default())); let mut engine = get_order_matching_engine( instrument_es.clone(), - Rc::new(msgbus), + Rc::new(RefCell::new(msgbus)), Some(cache.clone()), None, Some(engine_config), @@ -1441,7 +1475,7 @@ mod tests { let cache = Rc::new(RefCell::new(Cache::default())); let mut engine = get_order_matching_engine( instrument_es.clone(), - Rc::new(msgbus), + Rc::new(RefCell::new(msgbus)), Some(cache.clone()), None, Some(engine_config), @@ -1524,8 +1558,13 @@ mod tests { ); // Create engine and process order - let mut engine = - get_order_matching_engine(instrument_es.clone(), Rc::new(msgbus), None, None, None); + let mut engine = get_order_matching_engine( + instrument_es.clone(), + Rc::new(RefCell::new(msgbus)), + None, + None, + None, + ); let market_order_buy = TestOrderStubs::market_order( instrument_es.id(), OrderSide::Buy, diff --git a/nautilus_core/backtest/src/models/fee.rs b/nautilus_core/backtest/src/models/fee.rs index d8018c1ad5f5..70cdae3cee37 100644 --- a/nautilus_core/backtest/src/models/fee.rs +++ b/nautilus_core/backtest/src/models/fee.rs @@ -31,6 +31,12 @@ pub trait FeeModel { ) -> anyhow::Result; } +#[derive(Clone, Debug)] +pub enum FeeModelAny { + Fixed(FixedFeeModel), + MakerTaker(MakerTakerFeeModel), +} + #[derive(Debug, Clone)] pub struct FixedFeeModel { commission: Money, diff --git a/nautilus_core/backtest/src/models/latency.rs b/nautilus_core/backtest/src/models/latency.rs new file mode 100644 index 000000000000..afa150cef041 --- /dev/null +++ b/nautilus_core/backtest/src/models/latency.rs @@ -0,0 +1,16 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2024 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +pub struct LatencyModel; diff --git a/nautilus_core/backtest/src/models/mod.rs b/nautilus_core/backtest/src/models/mod.rs index bad8aa18af76..f5e35259b7cb 100644 --- a/nautilus_core/backtest/src/models/mod.rs +++ b/nautilus_core/backtest/src/models/mod.rs @@ -15,3 +15,4 @@ pub mod fee; pub mod fill; +pub mod latency; diff --git a/nautilus_core/backtest/src/modules/mod.rs b/nautilus_core/backtest/src/modules/mod.rs new file mode 100644 index 000000000000..94701c3464b6 --- /dev/null +++ b/nautilus_core/backtest/src/modules/mod.rs @@ -0,0 +1,29 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2024 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use nautilus_common::logging::logger::Logger; +use nautilus_core::nanos::UnixNanos; +use nautilus_model::data::Data; + +use crate::exchange::SimulatedExchange; + +#[warn(dead_code)] +pub trait SimulationModule { + fn register_venue(&self, exchange: SimulatedExchange); + fn pre_process(&self, data: Data); + fn process(&self, ts_now: UnixNanos); + fn log_diagnostics(&self, logger: Logger); + fn reset(&self); +} From e6a14d5a69b61b9a813280309f36abf8fdc3883d Mon Sep 17 00:00:00 2001 From: faysou Date: Wed, 14 Aug 2024 13:36:02 +0100 Subject: [PATCH 35/72] Improve customdataclass constructor to use positional arguments (#1850) --- nautilus_trader/model/custom.py | 19 +++++++++++-------- tests/unit_tests/model/test_custom_data.py | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/nautilus_trader/model/custom.py b/nautilus_trader/model/custom.py index 480becf2dc0c..c19d12a5df98 100644 --- a/nautilus_trader/model/custom.py +++ b/nautilus_trader/model/custom.py @@ -26,22 +26,25 @@ def customdataclass(*args, **kwargs): # noqa: C901 (too complex) def wrapper(cls): # noqa: C901 (too complex) + create_init = False + if cls.__init__ is object.__init__: + create_init = True + + cls = dataclass(cls, **kwargs) - def __init__(self, ts_event: int = 0, ts_init: int = 0, **kwargs): - for key, value in kwargs.items(): - if key in self.__class__.__annotations__: - setattr(self, key, value) - else: - raise ValueError(f"Unexpected keyword argument: {key}") + if create_init: + # cls.fields_init allows to use positional arguments for parameters other than ts_event and ts_init + cls.fields_init = cls.__init__ + + def __init__(self, ts_event: int = 0, ts_init: int = 0, *args2, **kwargs2): + self.fields_init(*args2, **kwargs2) self._ts_event = ts_event self._ts_init = ts_init cls.__init__ = __init__ - cls = dataclass(cls, **kwargs) - if "ts_event" not in cls.__dict__: @property diff --git a/tests/unit_tests/model/test_custom_data.py b/tests/unit_tests/model/test_custom_data.py index 30b21371cf70..fc5ea2404529 100644 --- a/tests/unit_tests/model/test_custom_data.py +++ b/tests/unit_tests/model/test_custom_data.py @@ -39,7 +39,7 @@ def test_customdata_decorator_properties() -> None: def test_customdata_decorator_dict() -> None: # Arrange - data = GreeksTestData(1, 2) + data = GreeksTestData(1, 2, InstrumentId.from_str("ES.GLBX"), 0.0) # Act data_dict = data.to_dict() From 9df8e2325acfe4c51d1693507a07883217a844b2 Mon Sep 17 00:00:00 2001 From: Ishan Bhanuka Date: Thu, 15 Aug 2024 06:24:35 -0400 Subject: [PATCH 36/72] Improve modeling and handling for errors (#1849) Co-authored-by: Chris Sellers --- .../adapters/src/databento/decode.rs | 160 +++---- .../adapters/src/databento/symbology.rs | 8 +- nautilus_core/backtest/src/matching_engine.rs | 3 +- nautilus_core/backtest/src/models/fee.rs | 8 +- nautilus_core/backtest/src/models/fill.rs | 10 +- nautilus_core/clippy.toml | 1 + nautilus_core/common/src/cache/mod.rs | 34 +- nautilus_core/common/src/clock.rs | 27 +- nautilus_core/common/src/factories.rs | 14 +- .../common/src/generators/client_order_id.rs | 35 +- nautilus_core/common/src/generators/mod.rs | 3 +- .../common/src/generators/order_list_id.rs | 35 +- nautilus_core/common/src/logging/logger.rs | 4 +- nautilus_core/common/src/timer.rs | 11 +- nautilus_core/common/src/xrate.rs | 9 +- nautilus_core/core/src/correctness.rs | 67 ++- nautilus_core/core/src/nanos.rs | 6 +- nautilus_core/core/src/uuid.rs | 8 +- nautilus_core/data/src/aggregation.rs | 187 ++++---- nautilus_core/indicators/src/stubs.rs | 4 +- .../infrastructure/src/redis/cache.rs | 4 +- .../infrastructure/src/sql/models/data.rs | 2 +- .../src/sql/models/instruments.rs | 22 +- .../infrastructure/src/sql/models/types.rs | 3 +- .../tests/test_cache_database_postgres.rs | 27 +- .../tests/test_cache_postgres.rs | 6 +- nautilus_core/model/src/accounts/base.rs | 18 +- nautilus_core/model/src/accounts/cash.rs | 13 +- nautilus_core/model/src/accounts/margin.rs | 21 +- nautilus_core/model/src/data/bar.rs | 29 +- nautilus_core/model/src/data/mod.rs | 4 +- nautilus_core/model/src/data/quote.rs | 18 +- nautilus_core/model/src/data/stubs.rs | 16 +- .../model/src/events/account/stubs.rs | 24 +- .../model/src/events/order/filled.rs | 2 +- .../model/src/events/order/initialized.rs | 2 +- nautilus_core/model/src/events/order/stubs.rs | 12 +- nautilus_core/model/src/ffi/data/bar.rs | 10 +- nautilus_core/model/src/ffi/data/order.rs | 4 +- nautilus_core/model/src/ffi/data/quote.rs | 8 +- nautilus_core/model/src/ffi/data/trade.rs | 4 +- .../model/src/ffi/identifiers/trade_id.rs | 2 +- nautilus_core/model/src/ffi/types/currency.rs | 3 +- nautilus_core/model/src/ffi/types/money.rs | 2 +- nautilus_core/model/src/ffi/types/price.rs | 4 +- nautilus_core/model/src/ffi/types/quantity.rs | 4 +- .../model/src/identifiers/account_id.rs | 31 +- .../model/src/identifiers/client_id.rs | 15 +- .../model/src/identifiers/client_order_id.rs | 17 +- .../model/src/identifiers/component_id.rs | 15 +- .../src/identifiers/exec_algorithm_id.rs | 15 +- .../model/src/identifiers/instrument_id.rs | 48 ++- nautilus_core/model/src/identifiers/macros.rs | 10 +- nautilus_core/model/src/identifiers/mod.rs | 2 - .../model/src/identifiers/order_list_id.rs | 15 +- .../model/src/identifiers/position_id.rs | 15 +- .../model/src/identifiers/strategy_id.rs | 19 +- nautilus_core/model/src/identifiers/symbol.rs | 15 +- .../model/src/identifiers/trade_id.rs | 35 +- .../model/src/identifiers/trader_id.rs | 17 +- nautilus_core/model/src/identifiers/venue.rs | 17 +- .../model/src/identifiers/venue_order_id.rs | 15 +- nautilus_core/model/src/instruments/any.rs | 4 +- .../model/src/instruments/crypto_future.rs | 20 +- .../model/src/instruments/crypto_perpetual.rs | 20 +- .../model/src/instruments/currency_pair.rs | 20 +- nautilus_core/model/src/instruments/equity.rs | 15 +- .../model/src/instruments/futures_contract.rs | 20 +- .../model/src/instruments/futures_spread.rs | 20 +- nautilus_core/model/src/instruments/mod.rs | 8 +- .../model/src/instruments/options_contract.rs | 17 +- .../model/src/instruments/options_spread.rs | 20 +- nautilus_core/model/src/instruments/stubs.rs | 20 +- .../model/src/instruments/synthetic.rs | 7 +- nautilus_core/model/src/orderbook/book.rs | 2 +- nautilus_core/model/src/orders/list.rs | 12 +- nautilus_core/model/src/orders/stubs.rs | 10 +- nautilus_core/model/src/position.rs | 401 +++++++----------- nautilus_core/model/src/python/data/bar.rs | 10 +- nautilus_core/model/src/python/data/delta.rs | 4 +- nautilus_core/model/src/python/data/depth.rs | 8 +- nautilus_core/model/src/python/data/quote.rs | 24 +- nautilus_core/model/src/python/data/trade.rs | 16 +- .../model/src/python/events/account/state.rs | 2 +- .../src/python/identifiers/instrument_id.rs | 8 +- .../model/src/python/identifiers/trade_id.rs | 12 +- .../src/python/instruments/crypto_future.rs | 3 +- .../python/instruments/crypto_perpetual.rs | 3 +- .../src/python/instruments/currency_pair.rs | 3 +- .../model/src/python/instruments/equity.rs | 3 +- .../python/instruments/futures_contract.rs | 3 +- .../src/python/instruments/futures_spread.rs | 3 +- .../python/instruments/options_contract.rs | 3 +- .../src/python/instruments/options_spread.rs | 3 +- nautilus_core/model/src/python/macros.rs | 13 +- .../model/src/python/orders/limit.rs | 10 +- .../model/src/python/orders/market.rs | 10 +- .../model/src/python/orders/stop_limit.rs | 10 +- nautilus_core/model/src/python/position.rs | 2 +- .../model/src/python/types/balance.rs | 26 +- .../model/src/python/types/currency.rs | 8 +- nautilus_core/model/src/python/types/money.rs | 12 +- nautilus_core/model/src/python/types/price.rs | 16 +- .../model/src/python/types/quantity.rs | 20 +- nautilus_core/model/src/stubs.rs | 20 +- nautilus_core/model/src/types/balance.rs | 16 +- nautilus_core/model/src/types/currency.rs | 34 +- nautilus_core/model/src/types/money.rs | 47 +- nautilus_core/model/src/types/price.rs | 81 ++-- nautilus_core/model/src/types/quantity.rs | 146 +++---- nautilus_core/model/src/types/stubs.rs | 4 +- nautilus_core/persistence/src/arrow/bar.rs | 10 +- nautilus_core/persistence/src/arrow/delta.rs | 4 +- nautilus_core/persistence/src/arrow/depth.rs | 8 +- nautilus_core/persistence/src/arrow/quote.rs | 12 +- nautilus_core/persistence/src/arrow/trade.rs | 8 +- .../model/objects/test_money_pyo3.py | 6 +- .../model/objects/test_price_pyo3.py | 8 +- .../model/objects/test_quantity_pyo3.py | 6 +- tests/unit_tests/model/test_currency_pyo3.py | 2 +- tests/unit_tests/model/test_identifiers.py | 4 +- .../unit_tests/model/test_identifiers_pyo3.py | 6 +- 122 files changed, 1098 insertions(+), 1374 deletions(-) diff --git a/nautilus_core/adapters/src/databento/decode.rs b/nautilus_core/adapters/src/databento/decode.rs index 34ae62d13231..7b523d73d741 100644 --- a/nautilus_core/adapters/src/databento/decode.rs +++ b/nautilus_core/adapters/src/databento/decode.rs @@ -213,23 +213,23 @@ pub fn parse_status_trading_event(value: u16) -> anyhow::Result> { } pub fn decode_price(value: i64, precision: u8) -> anyhow::Result { - match value { + Ok(match value { 0 | i64::MAX => Price::new(10f64.powi(-i32::from(precision)), precision), _ => Price::from_raw(value, precision), - } + }) } pub fn decode_optional_price(value: i64, precision: u8) -> anyhow::Result> { match value { i64::MAX => Ok(None), - _ => Ok(Some(Price::from_raw(value, precision)?)), + _ => Ok(Some(Price::from_raw(value, precision))), } } pub fn decode_optional_quantity_i32(value: i32) -> anyhow::Result> { match value { i32::MAX => Ok(None), - _ => Ok(Some(Quantity::new(f64::from(value), 0)?)), + _ => Ok(Some(Quantity::new(f64::from(value), 0))), } } @@ -258,7 +258,7 @@ pub fn decode_equity_v1( ) -> anyhow::Result { let currency = Currency::USD(); // TODO: Hard coding of US equities for now - Equity::new( + Ok(Equity::new( instrument_id, instrument_id.symbol, None, // No ISIN available yet @@ -269,14 +269,14 @@ pub fn decode_equity_v1( None, // TBD None, // TBD None, // TBD - Some(Quantity::new(f64::from(msg.min_lot_size_round_lot), 0)?), + Some(Quantity::new(f64::from(msg.min_lot_size_round_lot), 0)), None, // TBD None, // TBD None, // TBD None, // TBD msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } pub fn decode_futures_contract_v1( @@ -299,7 +299,7 @@ pub fn decode_futures_contract_v1( other => f64::from(other), }; - FuturesContract::new( + Ok(FuturesContract::new( instrument_id, instrument_id.symbol, asset_class.unwrap_or(AssetClass::Commodity), @@ -310,8 +310,8 @@ pub fn decode_futures_contract_v1( currency, currency.precision, decode_price(msg.min_price_increment, currency.precision)?, - Quantity::new(unit_of_measure_qty, 0)?, - Quantity::new(lot_size_round, 0)?, + Quantity::new(unit_of_measure_qty, 0), + Quantity::new(lot_size_round, 0), None, // TBD None, // TBD None, // TBD @@ -320,7 +320,7 @@ pub fn decode_futures_contract_v1( None, // TBD msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } pub fn decode_futures_spread_v1( @@ -344,7 +344,7 @@ pub fn decode_futures_spread_v1( other => f64::from(other), }; - FuturesSpread::new( + Ok(FuturesSpread::new( instrument_id, instrument_id.symbol, asset_class.unwrap_or(AssetClass::Commodity), @@ -356,8 +356,8 @@ pub fn decode_futures_spread_v1( currency, currency.precision, decode_price(msg.min_price_increment, currency.precision)?, - Quantity::new(unit_of_measure_qty, 0)?, - Quantity::new(lot_size_round, 0)?, + Quantity::new(unit_of_measure_qty, 0), + Quantity::new(lot_size_round, 0), None, // TBD None, // TBD None, // TBD @@ -366,7 +366,7 @@ pub fn decode_futures_spread_v1( None, // TBD msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } pub fn decode_options_contract_v1( @@ -395,7 +395,7 @@ pub fn decode_options_contract_v1( other => f64::from(other), }; - OptionsContract::new( + Ok(OptionsContract::new( instrument_id, instrument_id.symbol, asset_class_opt.unwrap_or(AssetClass::Commodity), @@ -404,12 +404,12 @@ pub fn decode_options_contract_v1( parse_option_kind(msg.instrument_class)?, msg.activation.into(), msg.expiration.into(), - Price::from_raw(msg.strike_price, currency.precision)?, + Price::from_raw(msg.strike_price, currency.precision), currency, currency.precision, decode_price(msg.min_price_increment, currency.precision)?, - Quantity::new(unit_of_measure_qty, 0)?, - Quantity::new(lot_size_round, 0)?, + Quantity::new(unit_of_measure_qty, 0), + Quantity::new(lot_size_round, 0), None, // TBD None, // TBD None, // TBD @@ -418,7 +418,7 @@ pub fn decode_options_contract_v1( None, msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } pub fn decode_options_spread_v1( @@ -448,7 +448,7 @@ pub fn decode_options_spread_v1( other => f64::from(other), }; - OptionsSpread::new( + Ok(OptionsSpread::new( instrument_id, instrument_id.symbol, asset_class_opt.unwrap_or(AssetClass::Commodity), @@ -460,8 +460,8 @@ pub fn decode_options_spread_v1( currency, currency.precision, decode_price(msg.min_price_increment, currency.precision)?, - Quantity::new(unit_of_measure_qty, 0)?, - Quantity::new(lot_size_round, 0)?, + Quantity::new(unit_of_measure_qty, 0), + Quantity::new(lot_size_round, 0), None, // TBD None, // TBD None, // TBD @@ -470,7 +470,7 @@ pub fn decode_options_spread_v1( None, // TBD msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } #[must_use] @@ -490,10 +490,10 @@ pub fn decode_mbo_msg( if include_trades { let trade = TradeTick::new( instrument_id, - Price::from_raw(msg.price, price_precision)?, - Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0)?, + Price::from_raw(msg.price, price_precision), + Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0), parse_aggressor_side(msg.side), - TradeId::new(itoa::Buffer::new().format(msg.sequence))?, + TradeId::new(itoa::Buffer::new().format(msg.sequence)), msg.ts_recv.into(), ts_init, ); @@ -505,8 +505,8 @@ pub fn decode_mbo_msg( let order = BookOrder::new( side, - Price::from_raw(msg.price, price_precision)?, - Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0)?, + Price::from_raw(msg.price, price_precision), + Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0), msg.order_id, ); @@ -531,10 +531,10 @@ pub fn decode_trade_msg( ) -> anyhow::Result { let trade = TradeTick::new( instrument_id, - Price::from_raw(msg.price, price_precision)?, - Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0)?, + Price::from_raw(msg.price, price_precision), + Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0), parse_aggressor_side(msg.side), - TradeId::new(itoa::Buffer::new().format(msg.sequence))?, + TradeId::new(itoa::Buffer::new().format(msg.sequence)), msg.ts_recv.into(), ts_init, ); @@ -551,20 +551,20 @@ pub fn decode_tbbo_msg( let top_level = &msg.levels[0]; let quote = QuoteTick::new( instrument_id, - Price::from_raw(top_level.bid_px, price_precision)?, - Price::from_raw(top_level.ask_px, price_precision)?, - Quantity::from_raw(u64::from(top_level.bid_sz) * FIXED_SCALAR as u64, 0)?, - Quantity::from_raw(u64::from(top_level.ask_sz) * FIXED_SCALAR as u64, 0)?, + Price::from_raw(top_level.bid_px, price_precision), + Price::from_raw(top_level.ask_px, price_precision), + Quantity::from_raw(u64::from(top_level.bid_sz) * FIXED_SCALAR as u64, 0), + Quantity::from_raw(u64::from(top_level.ask_sz) * FIXED_SCALAR as u64, 0), msg.ts_recv.into(), ts_init, )?; let trade = TradeTick::new( instrument_id, - Price::from_raw(msg.price, price_precision)?, - Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0)?, + Price::from_raw(msg.price, price_precision), + Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0), parse_aggressor_side(msg.side), - TradeId::new(itoa::Buffer::new().format(msg.sequence))?, + TradeId::new(itoa::Buffer::new().format(msg.sequence)), msg.ts_recv.into(), ts_init, ); @@ -582,10 +582,10 @@ pub fn decode_mbp1_msg( let top_level = &msg.levels[0]; let quote = QuoteTick::new( instrument_id, - Price::from_raw(top_level.bid_px, price_precision)?, - Price::from_raw(top_level.ask_px, price_precision)?, - Quantity::from_raw(u64::from(top_level.bid_sz) * FIXED_SCALAR as u64, 0)?, - Quantity::from_raw(u64::from(top_level.ask_sz) * FIXED_SCALAR as u64, 0)?, + Price::from_raw(top_level.bid_px, price_precision), + Price::from_raw(top_level.ask_px, price_precision), + Quantity::from_raw(u64::from(top_level.bid_sz) * FIXED_SCALAR as u64, 0), + Quantity::from_raw(u64::from(top_level.ask_sz) * FIXED_SCALAR as u64, 0), msg.ts_recv.into(), ts_init, )?; @@ -593,10 +593,10 @@ pub fn decode_mbp1_msg( let maybe_trade = if include_trades && msg.action as u8 as char == 'T' { Some(TradeTick::new( instrument_id, - Price::from_raw(msg.price, price_precision)?, - Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0)?, + Price::from_raw(msg.price, price_precision), + Quantity::from_raw(u64::from(msg.size) * FIXED_SCALAR as u64, 0), parse_aggressor_side(msg.side), - TradeId::new(itoa::Buffer::new().format(msg.sequence))?, + TradeId::new(itoa::Buffer::new().format(msg.sequence)), msg.ts_recv.into(), ts_init, )) @@ -621,15 +621,15 @@ pub fn decode_mbp10_msg( for level in &msg.levels { let bid_order = BookOrder::new( OrderSide::Buy, - Price::from_raw(level.bid_px, price_precision)?, - Quantity::from_raw(u64::from(level.bid_sz) * FIXED_SCALAR as u64, 0)?, + Price::from_raw(level.bid_px, price_precision), + Quantity::from_raw(u64::from(level.bid_sz) * FIXED_SCALAR as u64, 0), 0, ); let ask_order = BookOrder::new( OrderSide::Sell, - Price::from_raw(level.ask_px, price_precision)?, - Quantity::from_raw(u64::from(level.ask_sz) * FIXED_SCALAR as u64, 0)?, + Price::from_raw(level.ask_px, price_precision), + Quantity::from_raw(u64::from(level.ask_sz) * FIXED_SCALAR as u64, 0), 0, ); @@ -731,11 +731,11 @@ pub fn decode_ohlcv_msg( let bar = Bar::new( bar_type, - Price::from_raw(msg.open, price_precision)?, - Price::from_raw(msg.high, price_precision)?, - Price::from_raw(msg.low, price_precision)?, - Price::from_raw(msg.close, price_precision)?, - Quantity::from_raw(msg.volume * FIXED_SCALAR as u64, 0)?, + Price::from_raw(msg.open, price_precision), + Price::from_raw(msg.high, price_precision), + Price::from_raw(msg.low, price_precision), + Price::from_raw(msg.close, price_precision), + Quantity::from_raw(msg.volume * FIXED_SCALAR as u64, 0), ts_event, ts_init, )?; @@ -902,7 +902,7 @@ pub fn decode_equity( ) -> anyhow::Result { let currency = Currency::USD(); // TODO: Hard coding of US equities for now - Equity::new( + Ok(Equity::new( instrument_id, instrument_id.symbol, None, // No ISIN available yet @@ -913,14 +913,14 @@ pub fn decode_equity( None, // TBD None, // TBD None, // TBD - Some(Quantity::new(f64::from(msg.min_lot_size_round_lot), 0)?), + Some(Quantity::new(f64::from(msg.min_lot_size_round_lot), 0)), None, // TBD None, // TBD None, // TBD None, // TBD msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } pub fn decode_futures_contract( @@ -943,7 +943,7 @@ pub fn decode_futures_contract( other => f64::from(other), }; - FuturesContract::new( + Ok(FuturesContract::new( instrument_id, instrument_id.symbol, asset_class.unwrap_or(AssetClass::Commodity), @@ -954,8 +954,8 @@ pub fn decode_futures_contract( currency, currency.precision, decode_price(msg.min_price_increment, currency.precision)?, - Quantity::new(unit_of_measure_qty, 0)?, - Quantity::new(lot_size_round, 0)?, + Quantity::new(unit_of_measure_qty, 0), + Quantity::new(lot_size_round, 0), None, // TBD None, // TBD None, // TBD @@ -964,7 +964,7 @@ pub fn decode_futures_contract( None, // TBD msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } pub fn decode_futures_spread( @@ -988,7 +988,7 @@ pub fn decode_futures_spread( other => f64::from(other), }; - FuturesSpread::new( + Ok(FuturesSpread::new( instrument_id, instrument_id.symbol, asset_class.unwrap_or(AssetClass::Commodity), @@ -1000,8 +1000,8 @@ pub fn decode_futures_spread( currency, currency.precision, decode_price(msg.min_price_increment, currency.precision)?, - Quantity::new(unit_of_measure_qty, 0)?, - Quantity::new(lot_size_round, 0)?, + Quantity::new(unit_of_measure_qty, 0), + Quantity::new(lot_size_round, 0), None, // TBD None, // TBD None, // TBD @@ -1010,7 +1010,7 @@ pub fn decode_futures_spread( None, // TBD msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } pub fn decode_options_contract( @@ -1039,7 +1039,7 @@ pub fn decode_options_contract( other => f64::from(other), }; - OptionsContract::new( + Ok(OptionsContract::new( instrument_id, instrument_id.symbol, asset_class_opt.unwrap_or(AssetClass::Commodity), @@ -1048,12 +1048,12 @@ pub fn decode_options_contract( parse_option_kind(msg.instrument_class)?, msg.activation.into(), msg.expiration.into(), - Price::from_raw(msg.strike_price, currency.precision)?, + Price::from_raw(msg.strike_price, currency.precision), currency, currency.precision, decode_price(msg.min_price_increment, currency.precision)?, - Quantity::new(unit_of_measure_qty, 0)?, - Quantity::new(lot_size_round, 0)?, + Quantity::new(unit_of_measure_qty, 0), + Quantity::new(lot_size_round, 0), None, // TBD None, // TBD None, // TBD @@ -1062,7 +1062,7 @@ pub fn decode_options_contract( None, // TBD msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } pub fn decode_options_spread( @@ -1092,7 +1092,7 @@ pub fn decode_options_spread( other => f64::from(other), }; - OptionsSpread::new( + Ok(OptionsSpread::new( instrument_id, instrument_id.symbol, asset_class_opt.unwrap_or(AssetClass::Commodity), @@ -1104,8 +1104,8 @@ pub fn decode_options_spread( currency, currency.precision, decode_price(msg.min_price_increment, currency.precision)?, - Quantity::new(unit_of_measure_qty, 0)?, - Quantity::new(lot_size_round, 0)?, + Quantity::new(unit_of_measure_qty, 0), + Quantity::new(lot_size_round, 0), None, // TBD None, // TBD None, // TBD @@ -1114,7 +1114,7 @@ pub fn decode_options_spread( None, // TBD msg.ts_recv.into(), // More accurate and reliable timestamp ts_init, - ) + )) } pub fn decode_imbalance_msg( @@ -1125,11 +1125,11 @@ pub fn decode_imbalance_msg( ) -> anyhow::Result { DatabentoImbalance::new( instrument_id, - Price::from_raw(msg.ref_price, price_precision)?, - Price::from_raw(msg.cont_book_clr_price, price_precision)?, - Price::from_raw(msg.auct_interest_clr_price, price_precision)?, - Quantity::new(f64::from(msg.paired_qty), 0)?, - Quantity::new(f64::from(msg.total_imbalance_qty), 0)?, + Price::from_raw(msg.ref_price, price_precision), + Price::from_raw(msg.cont_book_clr_price, price_precision), + Price::from_raw(msg.auct_interest_clr_price, price_precision), + Quantity::new(f64::from(msg.paired_qty), 0), + Quantity::new(f64::from(msg.total_imbalance_qty), 0), parse_order_side(msg.side), msg.significant_imbalance as c_char, msg.hd.ts_event.into(), diff --git a/nautilus_core/adapters/src/databento/symbology.rs b/nautilus_core/adapters/src/databento/symbology.rs index ed7345836d4e..c8708c40534f 100644 --- a/nautilus_core/adapters/src/databento/symbology.rs +++ b/nautilus_core/adapters/src/databento/symbology.rs @@ -95,7 +95,7 @@ pub fn infer_symbology_type(symbol: &str) -> String { } pub fn check_consistent_symbology(symbols: &[&str]) -> anyhow::Result<()> { - check_slice_not_empty(symbols, stringify!(symbols))?; + check_slice_not_empty(symbols, stringify!(symbols)).unwrap(); // SAFETY: We checked len so know there must be at least one symbol let first_symbol = symbols.first().unwrap(); @@ -148,12 +148,8 @@ mod tests { #[rstest] fn test_check_consistent_symbology_when_empty_symbols() { let symbols: Vec<&str> = vec![]; - let result = check_consistent_symbology(&symbols); + let result = std::panic::catch_unwind(|| check_consistent_symbology(&symbols)); assert!(result.is_err()); - assert_eq!( - result.err().unwrap().to_string(), - "Condition failed: the 'symbols' slice `&[&str]` was empty" - ); } #[rstest] diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index 21ade348f86e..ffcda05b2fc9 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -406,8 +406,7 @@ impl OrderMatchingEngine { if self.oms_type == OmsType::Netting { let position_id = PositionId::new( format!("{}-{}", order.instrument_id(), order.strategy_id()).as_str(), - ) - .unwrap(); + ); cache_borrow.position(&position_id) } else { None diff --git a/nautilus_core/backtest/src/models/fee.rs b/nautilus_core/backtest/src/models/fee.rs index 70cdae3cee37..2974dcce4ecc 100644 --- a/nautilus_core/backtest/src/models/fee.rs +++ b/nautilus_core/backtest/src/models/fee.rs @@ -49,7 +49,7 @@ impl FixedFeeModel { if commission.as_f64() < 0.0 { anyhow::bail!("Commission must be greater than or equal to zero.") } - let zero_commission = Money::new(0.0, commission.currency)?; + let zero_commission = Money::new(0.0, commission.currency); Ok(Self { commission, zero_commission, @@ -92,8 +92,8 @@ impl FeeModel for MakerTakerFeeModel { Some(LiquiditySide::NoLiquiditySide) | None => anyhow::bail!("Liquidity side not set."), }; match instrument.is_inverse() { - true => Ok(Money::new(commission, instrument.base_currency().unwrap())?), - false => Ok(Money::new(commission, instrument.quote_currency())?), + true => Ok(Money::new(commission, instrument.base_currency().unwrap())), + false => Ok(Money::new(commission, instrument.quote_currency())), } } } @@ -113,7 +113,7 @@ mod tests { #[rstest] fn test_fixed_model_single_fill() { - let expected_commission = Money::new(1.0, Currency::USD()).unwrap(); + let expected_commission = Money::new(1.0, Currency::USD()); let aud_usd = InstrumentAny::CurrencyPair(audusd_sim()); let fee_model = FixedFeeModel::new(expected_commission, None).unwrap(); let market_order = TestOrderStubs::market_order( diff --git a/nautilus_core/backtest/src/models/fill.rs b/nautilus_core/backtest/src/models/fill.rs index 55a6d78eda2c..42ad1c0a31f7 100644 --- a/nautilus_core/backtest/src/models/fill.rs +++ b/nautilus_core/backtest/src/models/fill.rs @@ -13,7 +13,7 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- -use nautilus_core::correctness::check_in_range_inclusive_f64; +use nautilus_core::correctness::{check_in_range_inclusive_f64, FAILED}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaChaRng; @@ -36,9 +36,11 @@ impl FillModel { prob_slippage: f64, random_seed: Option, ) -> anyhow::Result { - check_in_range_inclusive_f64(prob_fill_on_limit, 0.0, 1.0, "prob_fill_on_limit").unwrap(); - check_in_range_inclusive_f64(prob_fill_on_stop, 0.0, 1.0, "prob_fill_on_stop").unwrap(); - check_in_range_inclusive_f64(prob_slippage, 0.0, 1.0, "prob_slippage").unwrap(); + check_in_range_inclusive_f64(prob_fill_on_limit, 0.0, 1.0, "prob_fill_on_limit") + .expect(FAILED); + check_in_range_inclusive_f64(prob_fill_on_stop, 0.0, 1.0, "prob_fill_on_stop") + .expect(FAILED); + check_in_range_inclusive_f64(prob_slippage, 0.0, 1.0, "prob_slippage").expect(FAILED); let rng = match random_seed { Some(seed) => ChaChaRng::seed_from_u64(seed), None => ChaChaRng::from_entropy(), diff --git a/nautilus_core/clippy.toml b/nautilus_core/clippy.toml index e8f5cca84a22..4c4175b05adc 100644 --- a/nautilus_core/clippy.toml +++ b/nautilus_core/clippy.toml @@ -1,2 +1,3 @@ cognitive-complexity-threshold = 15 allow-expect-in-tests = true +allow-unwrap-in-tests = true diff --git a/nautilus_core/common/src/cache/mod.rs b/nautilus_core/common/src/cache/mod.rs index 381b71b54181..dba05bb5a213 100644 --- a/nautilus_core/common/src/cache/mod.rs +++ b/nautilus_core/common/src/cache/mod.rs @@ -29,7 +29,7 @@ use std::{ use bytes::Bytes; use database::CacheDatabaseAdapter; use nautilus_core::correctness::{ - check_key_not_in_map, check_predicate_false, check_slice_not_empty, check_valid_string, + check_key_not_in_map, check_predicate_false, check_slice_not_empty, check_valid_string, FAILED, }; use nautilus_model::{ accounts::any::AccountAny, @@ -1034,8 +1034,8 @@ impl Cache { /// The cache is agnostic to what the bytes actually represent (and how it may be serialized), /// which provides maximum flexibility. pub fn add(&mut self, key: &str, value: Bytes) -> anyhow::Result<()> { - check_valid_string(key, stringify!(key))?; - check_predicate_false(value.is_empty(), stringify!(value))?; + check_valid_string(key, stringify!(key)).expect(FAILED); + check_predicate_false(value.is_empty(), stringify!(value)).expect(FAILED); log::debug!("Adding general {key}"); self.general.insert(key.to_string(), value.clone()); @@ -1080,7 +1080,7 @@ impl Cache { /// Adds the given `quotes` to the cache. pub fn add_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> { - check_slice_not_empty(quotes, stringify!(quotes))?; + check_slice_not_empty(quotes, stringify!(quotes)).unwrap(); let instrument_id = quotes[0].instrument_id; log::debug!("Adding `QuoteTick`[{}] {}", quotes.len(), instrument_id); @@ -1124,7 +1124,7 @@ impl Cache { /// Adds the give `trades` to the cache. pub fn add_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> { - check_slice_not_empty(trades, stringify!(trades))?; + check_slice_not_empty(trades, stringify!(trades)).unwrap(); let instrument_id = trades[0].instrument_id; log::debug!("Adding `TradeTick`[{}] {}", trades.len(), instrument_id); @@ -1168,7 +1168,7 @@ impl Cache { /// Adds the given `bars` to the cache. pub fn add_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> { - check_slice_not_empty(bars, stringify!(bars))?; + check_slice_not_empty(bars, stringify!(bars)).unwrap(); let bar_type = bars[0].bar_type; log::debug!("Adding `Bar`[{}] {}", bars.len(), bar_type); @@ -1305,25 +1305,29 @@ impl Cache { &self.orders, stringify!(client_order_id), stringify!(orders), - )?; + ) + .unwrap(); check_key_not_in_map( &client_order_id, &self.orders, stringify!(client_order_id), stringify!(orders), - )?; + ) + .unwrap(); check_key_not_in_map( &client_order_id, &self.orders, stringify!(client_order_id), stringify!(orders), - )?; + ) + .unwrap(); check_key_not_in_map( &client_order_id, &self.orders, stringify!(client_order_id), stringify!(orders), - )?; + ) + .unwrap(); }; log::debug!("Adding {:?}", order); @@ -1365,10 +1369,9 @@ impl Cache { .or_default() .insert(client_order_id); - // SAFETY: We can guarantee the `exec_spawn_id` is Some self.index .exec_spawn_orders - .entry(exec_spawn_id.unwrap()) + .entry(exec_spawn_id.expect("`exec_spawn_id` is guaranteed to exist")) .or_default() .insert(client_order_id); } @@ -2394,7 +2397,7 @@ impl Cache { /// Gets a reference to the general object value for the given `key` (if found). pub fn get(&self, key: &str) -> anyhow::Result> { - check_valid_string(key, stringify!(key))?; + check_valid_string(key, stringify!(key)).expect(FAILED); Ok(self.general.get(key)) } @@ -2419,7 +2422,6 @@ impl Cache { (quote.ask_price.as_f64() + quote.bid_price.as_f64()) / 2.0, quote.bid_price.precision + 1, ) - .expect("Error calculating mid price") }) }), PriceType::Last => self @@ -3017,7 +3019,7 @@ mod tests { &order, &audusd_sim, None, - Some(PositionId::new("P-123456").unwrap()), + Some(PositionId::new("P-123456")), None, None, None, @@ -3025,7 +3027,7 @@ mod tests { None, None, ); - let position = Position::new(&audusd_sim, fill.into()).unwrap(); + let position = Position::new(&audusd_sim, fill.into()); cache .add_position(position.clone(), OmsType::Netting) .unwrap(); diff --git a/nautilus_core/common/src/clock.rs b/nautilus_core/common/src/clock.rs index c70e335af990..99121cd93f34 100644 --- a/nautilus_core/common/src/clock.rs +++ b/nautilus_core/common/src/clock.rs @@ -19,7 +19,7 @@ use std::{collections::HashMap, ops::Deref}; use chrono::{DateTime, Utc}; use nautilus_core::{ - correctness::{check_positive_u64, check_predicate_true, check_valid_string}, + correctness::{check_positive_u64, check_predicate_true, check_valid_string, FAILED}, nanos::UnixNanos, time::{get_atomic_clock_realtime, AtomicTime}, }; @@ -146,7 +146,9 @@ impl TestClock { let handler = self.callbacks.get(&event.name).cloned().unwrap_or_else(|| { // If callback_py is None, use the default_callback_py // TODO: clone for now - self.default_callback.clone().unwrap() + self.default_callback + .clone() + .expect("Default callback should exist") }); create_time_event_handler(event, &handler) }) @@ -226,11 +228,12 @@ impl Clock for TestClock { alert_time_ns: UnixNanos, callback: Option, ) -> anyhow::Result<()> { - check_valid_string(name, stringify!(name))?; + check_valid_string(name, stringify!(name)).expect(FAILED); check_predicate_true( callback.is_some() | self.default_callback.is_some(), "All Python callbacks were `None`", - )?; + ) + .expect(FAILED); let name_ustr = Ustr::from(name); match callback { @@ -257,12 +260,13 @@ impl Clock for TestClock { stop_time_ns: Option, callback: Option, ) -> anyhow::Result<()> { - check_valid_string(name, "name")?; - check_positive_u64(interval_ns, stringify!(interval_ns))?; + check_valid_string(name, "name").expect(FAILED); + check_positive_u64(interval_ns, stringify!(interval_ns)).expect(FAILED); check_predicate_true( callback.is_some() | self.default_callback.is_some(), "All Python callbacks were `None`", - )?; + ) + .expect(FAILED); let name_ustr = Ustr::from(name); match callback { @@ -382,7 +386,7 @@ impl Clock for LiveClock { mut alert_time_ns: UnixNanos, callback: Option, ) -> anyhow::Result<()> { - check_valid_string(name, stringify!(name)).unwrap(); + check_valid_string(name, stringify!(name)).expect(FAILED); assert!( callback.is_some() | self.default_callback.is_some(), "No callbacks provided", @@ -411,12 +415,13 @@ impl Clock for LiveClock { stop_time_ns: Option, callback: Option, ) -> anyhow::Result<()> { - check_valid_string(name, stringify!(name))?; - check_positive_u64(interval_ns, stringify!(interval_ns))?; + check_valid_string(name, stringify!(name)).expect(FAILED); + check_positive_u64(interval_ns, stringify!(interval_ns)).expect(FAILED); check_predicate_true( callback.is_some() | self.default_callback.is_some(), "No callbacks provided", - )?; + ) + .expect(FAILED); let callback = match callback { Some(callback) => callback, diff --git a/nautilus_core/common/src/factories.rs b/nautilus_core/common/src/factories.rs index f24e06c2c817..b08c7e9653b0 100644 --- a/nautilus_core/common/src/factories.rs +++ b/nautilus_core/common/src/factories.rs @@ -172,7 +172,7 @@ pub mod tests { let client_order_id = order_factory.generate_client_order_id(); assert_eq!( client_order_id, - ClientOrderId::new("O-19700101-000000-001-001-1").unwrap() + ClientOrderId::new("O-19700101-000000-001-001-1") ); } @@ -181,7 +181,7 @@ pub mod tests { let order_list_id = order_factory.generate_order_list_id(); assert_eq!( order_list_id, - OrderListId::new("OL-19700101-000000-001-001-1").unwrap() + OrderListId::new("OL-19700101-000000-001-001-1") ); } @@ -191,7 +191,7 @@ pub mod tests { let client_order_id = order_factory.generate_client_order_id(); assert_eq!( client_order_id, - ClientOrderId::new("O-19700101-000000-001-001-11").unwrap() + ClientOrderId::new("O-19700101-000000-001-001-11") ); } @@ -201,7 +201,7 @@ pub mod tests { let order_list_id = order_factory.generate_order_list_id(); assert_eq!( order_list_id, - OrderListId::new("OL-19700101-000000-001-001-11").unwrap() + OrderListId::new("OL-19700101-000000-001-001-11") ); } @@ -214,11 +214,11 @@ pub mod tests { let order_list_id = order_factory.generate_order_list_id(); assert_eq!( client_order_id, - ClientOrderId::new("O-19700101-000000-001-001-1").unwrap() + ClientOrderId::new("O-19700101-000000-001-001-1") ); assert_eq!( order_list_id, - OrderListId::new("OL-19700101-000000-001-001-1").unwrap() + OrderListId::new("OL-19700101-000000-001-001-1") ); } @@ -248,7 +248,7 @@ pub mod tests { // assert_eq!(market_order.tags, None); assert_eq!( market_order.client_order_id(), - ClientOrderId::new("O-19700101-000000-001-001-1").unwrap() + ClientOrderId::new("O-19700101-000000-001-001-1") ); // assert_eq!(market_order.order_list_id(), None); } diff --git a/nautilus_core/common/src/generators/client_order_id.rs b/nautilus_core/common/src/generators/client_order_id.rs index 7e0c5a14f455..81690c651372 100644 --- a/nautilus_core/common/src/generators/client_order_id.rs +++ b/nautilus_core/common/src/generators/client_order_id.rs @@ -108,18 +108,9 @@ mod tests { let result2 = generator.generate(); let result3 = generator.generate(); - assert_eq!( - result1, - ClientOrderId::new("O-19700101-000000-001-001-1").unwrap() - ); - assert_eq!( - result2, - ClientOrderId::new("O-19700101-000000-001-001-2").unwrap() - ); - assert_eq!( - result3, - ClientOrderId::new("O-19700101-000000-001-001-3").unwrap() - ); + assert_eq!(result1, ClientOrderId::new("O-19700101-000000-001-001-1")); + assert_eq!(result2, ClientOrderId::new("O-19700101-000000-001-001-2")); + assert_eq!(result3, ClientOrderId::new("O-19700101-000000-001-001-3")); } #[rstest] @@ -129,18 +120,9 @@ mod tests { let result2 = generator.generate(); let result3 = generator.generate(); - assert_eq!( - result1, - ClientOrderId::new("O-19700101-000000-001-001-6").unwrap() - ); - assert_eq!( - result2, - ClientOrderId::new("O-19700101-000000-001-001-7").unwrap() - ); - assert_eq!( - result3, - ClientOrderId::new("O-19700101-000000-001-001-8").unwrap() - ); + assert_eq!(result1, ClientOrderId::new("O-19700101-000000-001-001-6")); + assert_eq!(result2, ClientOrderId::new("O-19700101-000000-001-001-7")); + assert_eq!(result3, ClientOrderId::new("O-19700101-000000-001-001-8")); } #[rstest] @@ -151,9 +133,6 @@ mod tests { generator.reset(); let result = generator.generate(); - assert_eq!( - result, - ClientOrderId::new("O-19700101-000000-001-001-1").unwrap() - ); + assert_eq!(result, ClientOrderId::new("O-19700101-000000-001-001-1")); } } diff --git a/nautilus_core/common/src/generators/mod.rs b/nautilus_core/common/src/generators/mod.rs index f332903bb1b7..89877843fdf3 100644 --- a/nautilus_core/common/src/generators/mod.rs +++ b/nautilus_core/common/src/generators/mod.rs @@ -22,7 +22,8 @@ pub mod position_id; use chrono::{DateTime, Datelike, Timelike}; fn get_datetime_tag(unix_ms: u64) -> String { - let now_utc = DateTime::from_timestamp_millis(unix_ms as i64).unwrap(); + let now_utc = DateTime::from_timestamp_millis(unix_ms as i64) + .expect("Milliseconds timestamp should be within valid range"); format!( "{}{:02}{:02}-{:02}{:02}{:02}", now_utc.year(), diff --git a/nautilus_core/common/src/generators/order_list_id.rs b/nautilus_core/common/src/generators/order_list_id.rs index c816d6799255..98f5fb42d331 100644 --- a/nautilus_core/common/src/generators/order_list_id.rs +++ b/nautilus_core/common/src/generators/order_list_id.rs @@ -108,18 +108,9 @@ mod tests { let result2 = generator.generate(); let result3 = generator.generate(); - assert_eq!( - result1, - OrderListId::new("OL-19700101-000000-001-001-1").unwrap() - ); - assert_eq!( - result2, - OrderListId::new("OL-19700101-000000-001-001-2").unwrap() - ); - assert_eq!( - result3, - OrderListId::new("OL-19700101-000000-001-001-3").unwrap() - ); + assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-1")); + assert_eq!(result2, OrderListId::new("OL-19700101-000000-001-001-2")); + assert_eq!(result3, OrderListId::new("OL-19700101-000000-001-001-3")); } #[rstest] @@ -129,18 +120,9 @@ mod tests { let result2 = generator.generate(); let result3 = generator.generate(); - assert_eq!( - result1, - OrderListId::new("OL-19700101-000000-001-001-6").unwrap() - ); - assert_eq!( - result2, - OrderListId::new("OL-19700101-000000-001-001-7").unwrap() - ); - assert_eq!( - result3, - OrderListId::new("OL-19700101-000000-001-001-8").unwrap() - ); + assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-6")); + assert_eq!(result2, OrderListId::new("OL-19700101-000000-001-001-7")); + assert_eq!(result3, OrderListId::new("OL-19700101-000000-001-001-8")); } #[rstest] @@ -151,9 +133,6 @@ mod tests { generator.reset(); let result = generator.generate(); - assert_eq!( - result, - OrderListId::new("OL-19700101-000000-001-001-1").unwrap() - ); + assert_eq!(result, OrderListId::new("OL-19700101-000000-001-001-1")); } } diff --git a/nautilus_core/common/src/logging/logger.rs b/nautilus_core/common/src/logging/logger.rs index c8e58031a4e9..2511aea8a39e 100644 --- a/nautilus_core/common/src/logging/logger.rs +++ b/nautilus_core/common/src/logging/logger.rs @@ -294,7 +294,9 @@ impl Log for Logger { } fn flush(&self) { - self.tx.send(LogEvent::Flush).unwrap(); + if let Err(e) = self.tx.send(LogEvent::Flush) { + eprintln!("Error sending flush log event: {e}"); + } } } diff --git a/nautilus_core/common/src/timer.rs b/nautilus_core/common/src/timer.rs index fcf76f9c1185..b60fbc73d280 100644 --- a/nautilus_core/common/src/timer.rs +++ b/nautilus_core/common/src/timer.rs @@ -27,8 +27,11 @@ use std::{ }; use nautilus_core::{ - correctness::check_valid_string, datetime::floor_to_nearest_microsecond, nanos::UnixNanos, - time::get_atomic_clock_realtime, uuid::UUID4, + correctness::{check_valid_string, FAILED}, + datetime::floor_to_nearest_microsecond, + nanos::UnixNanos, + time::get_atomic_clock_realtime, + uuid::UUID4, }; #[cfg(feature = "python")] use pyo3::{types::PyCapsule, IntoPy, PyObject, Python}; @@ -138,7 +141,7 @@ impl TestTimer { start_time_ns: UnixNanos, stop_time_ns: Option, ) -> anyhow::Result { - check_valid_string(name, stringify!(name))?; + check_valid_string(name, stringify!(name)).expect(FAILED); // SAFETY: Guaranteed to be non-zero let interval_ns = NonZeroU64::new(std::cmp::max(interval_ns, 1)).unwrap(); @@ -242,7 +245,7 @@ impl LiveTimer { stop_time_ns: Option, callback: EventHandler, ) -> anyhow::Result { - check_valid_string(name, stringify!(name))?; + check_valid_string(name, stringify!(name)).expect(FAILED); // SAFETY: Guaranteed to be non-zero let interval_ns = NonZeroU64::new(std::cmp::max(interval_ns, 1)).unwrap(); diff --git a/nautilus_core/common/src/xrate.rs b/nautilus_core/common/src/xrate.rs index 7e87d5c04fa7..910c1b34f5df 100644 --- a/nautilus_core/common/src/xrate.rs +++ b/nautilus_core/common/src/xrate.rs @@ -24,7 +24,7 @@ use std::collections::{HashMap, HashSet}; use itertools::Itertools; -use nautilus_core::correctness::{check_equal_usize, check_map_not_empty}; +use nautilus_core::correctness::{check_equal_usize, check_map_not_empty, FAILED}; use nautilus_model::{enums::PriceType, identifiers::Symbol, types::currency::Currency}; use rust_decimal::Decimal; use rust_decimal_macros::dec; @@ -42,14 +42,15 @@ pub fn get_exchange_rate( quotes_bid: HashMap, quotes_ask: HashMap, ) -> anyhow::Result { - check_map_not_empty("es_bid, stringify!(quotes_bid))?; - check_map_not_empty("es_ask, stringify!(quotes_ask))?; + check_map_not_empty("es_bid, stringify!(quotes_bid)).unwrap(); + check_map_not_empty("es_ask, stringify!(quotes_ask)).unwrap(); check_equal_usize( quotes_bid.len(), quotes_ask.len(), "quotes_bid.len()", "quotes_ask.len()", - )?; + ) + .expect(FAILED); if from_currency == to_currency { return Ok(DECIMAL_ONE); // No conversion necessary diff --git a/nautilus_core/core/src/correctness.rs b/nautilus_core/core/src/correctness.rs index e63660bdfbc8..790533e4bb76 100644 --- a/nautilus_core/core/src/correctness.rs +++ b/nautilus_core/core/src/correctness.rs @@ -30,12 +30,17 @@ use std::{ hash::Hash, }; -const FAILED: &str = "Condition failed:"; +/// A message prefix that can be used with calls to `expect` or other assertion-related functions. +/// +/// This constant provides a standard message that can be used to indicate a failure condition +/// when a predicate or condition does not hold true. It is typically used in conjunction with +/// functions like `expect` to provide a consistent error message. +pub const FAILED: &str = "Condition failed"; /// Checks the `predicate` is true. pub fn check_predicate_true(predicate: bool, fail_msg: &str) -> anyhow::Result<()> { if !predicate { - anyhow::bail!("{FAILED} {fail_msg}") + anyhow::bail!("{fail_msg}") } Ok(()) } @@ -43,7 +48,7 @@ pub fn check_predicate_true(predicate: bool, fail_msg: &str) -> anyhow::Result<( /// Checks the `predicate` is false. pub fn check_predicate_false(predicate: bool, fail_msg: &str) -> anyhow::Result<()> { if predicate { - anyhow::bail!("{FAILED} {fail_msg}") + anyhow::bail!("{fail_msg}") } Ok(()) } @@ -58,7 +63,7 @@ pub fn check_predicate_false(predicate: bool, fail_msg: &str) -> anyhow::Result< pub fn check_valid_string(s: &str, param: &str) -> anyhow::Result<()> { // Ensure string is only traversed once if s.is_empty() { - anyhow::bail!("{FAILED} invalid string for '{param}', was empty"); + anyhow::bail!("invalid string for '{param}', was empty"); } let mut has_non_whitespace = false; @@ -67,14 +72,12 @@ pub fn check_valid_string(s: &str, param: &str) -> anyhow::Result<()> { has_non_whitespace = true; } if !c.is_ascii() { - anyhow::bail!( - "{FAILED} invalid string for '{param}' contained a non-ASCII char, was '{s}'" - ); + anyhow::bail!("invalid string for '{param}' contained a non-ASCII char, was '{s}'"); } } if !has_non_whitespace { - anyhow::bail!("{FAILED} invalid string for '{param}', was all whitespace"); + anyhow::bail!("invalid string for '{param}', was all whitespace"); } Ok(()) @@ -97,7 +100,7 @@ pub fn check_valid_string_optional(s: Option<&str>, param: &str) -> anyhow::Resu /// Checks the string `s` contains the pattern `pat`. pub fn check_string_contains(s: &str, pat: &str, param: &str) -> anyhow::Result<()> { if !s.contains(pat) { - anyhow::bail!("{FAILED} invalid string for '{param}' did not contain '{pat}', was '{s}'") + anyhow::bail!("invalid string for '{param}' did not contain '{pat}', was '{s}'") } Ok(()) } @@ -111,7 +114,7 @@ pub fn check_equal( ) -> anyhow::Result<()> { if lhs != rhs { anyhow::bail!( - "{FAILED} '{lhs_param}' value of {lhs:?} was not equal to '{rhs_param}' value of {rhs:?}", + "'{lhs_param}' value of {lhs:?} was not equal to '{rhs_param}' value of {rhs:?}", ); } Ok(()) @@ -120,9 +123,7 @@ pub fn check_equal( /// Checks the `u8` values are equal. pub fn check_equal_u8(lhs: u8, rhs: u8, lhs_param: &str, rhs_param: &str) -> anyhow::Result<()> { if lhs != rhs { - anyhow::bail!( - "{FAILED} '{lhs_param}' u8 of {lhs} was not equal to '{rhs_param}' u8 of {rhs}" - ) + anyhow::bail!("'{lhs_param}' u8 of {lhs} was not equal to '{rhs_param}' u8 of {rhs}") } Ok(()) } @@ -135,9 +136,7 @@ pub fn check_equal_usize( rhs_param: &str, ) -> anyhow::Result<()> { if lhs != rhs { - anyhow::bail!( - "{FAILED} '{lhs_param}' usize of {lhs} was not equal to '{rhs_param}' usize of {rhs}" - ) + anyhow::bail!("'{lhs_param}' usize of {lhs} was not equal to '{rhs_param}' usize of {rhs}") } Ok(()) } @@ -145,7 +144,7 @@ pub fn check_equal_usize( /// Checks the `u64` value is positive (> 0). pub fn check_positive_u64(value: u64, param: &str) -> anyhow::Result<()> { if value == 0 { - anyhow::bail!("{FAILED} invalid u64 for '{param}' not positive, was {value}") + anyhow::bail!("invalid u64 for '{param}' not positive, was {value}") } Ok(()) } @@ -153,7 +152,7 @@ pub fn check_positive_u64(value: u64, param: &str) -> anyhow::Result<()> { /// Checks the `i64` value is positive (> 0). pub fn check_positive_i64(value: i64, param: &str) -> anyhow::Result<()> { if value <= 0 { - anyhow::bail!("{FAILED} invalid i64 for '{param}' not positive, was {value}") + anyhow::bail!("invalid i64 for '{param}' not positive, was {value}") } Ok(()) } @@ -161,10 +160,10 @@ pub fn check_positive_i64(value: i64, param: &str) -> anyhow::Result<()> { /// Checks the `f64` value is non-negative (< 0). pub fn check_non_negative_f64(value: f64, param: &str) -> anyhow::Result<()> { if value.is_nan() || value.is_infinite() { - anyhow::bail!("{FAILED} invalid f64 for '{param}', was {value}") + anyhow::bail!("invalid f64 for '{param}', was {value}") } if value < 0.0 { - anyhow::bail!("{FAILED} invalid f64 for '{param}' negative, was {value}") + anyhow::bail!("invalid f64 for '{param}' negative, was {value}") } Ok(()) } @@ -172,7 +171,7 @@ pub fn check_non_negative_f64(value: f64, param: &str) -> anyhow::Result<()> { /// Checks the `u8` value is in range [`l`, `r`] (inclusive). pub fn check_in_range_inclusive_u8(value: u8, l: u8, r: u8, param: &str) -> anyhow::Result<()> { if value < l || value > r { - anyhow::bail!("{FAILED} invalid u8 for '{param}' not in range [{l}, {r}], was {value}") + anyhow::bail!("invalid u8 for '{param}' not in range [{l}, {r}], was {value}") } Ok(()) } @@ -180,7 +179,7 @@ pub fn check_in_range_inclusive_u8(value: u8, l: u8, r: u8, param: &str) -> anyh /// Checks the `u64` value is range [`l`, `r`] (inclusive). pub fn check_in_range_inclusive_u64(value: u64, l: u64, r: u64, param: &str) -> anyhow::Result<()> { if value < l || value > r { - anyhow::bail!("{FAILED} invalid u64 for '{param}' not in range [{l}, {r}], was {value}") + anyhow::bail!("invalid u64 for '{param}' not in range [{l}, {r}], was {value}") } Ok(()) } @@ -188,7 +187,7 @@ pub fn check_in_range_inclusive_u64(value: u64, l: u64, r: u64, param: &str) -> /// Checks the `i64` value is in range [`l`, `r`] (inclusive). pub fn check_in_range_inclusive_i64(value: i64, l: i64, r: i64, param: &str) -> anyhow::Result<()> { if value < l || value > r { - anyhow::bail!("{FAILED} invalid i64 for '{param}' not in range [{l}, {r}], was {value}") + anyhow::bail!("invalid i64 for '{param}' not in range [{l}, {r}], was {value}") } Ok(()) } @@ -198,10 +197,10 @@ pub fn check_in_range_inclusive_f64(value: f64, l: f64, r: f64, param: &str) -> const EPSILON: f64 = 1e-15; // Epsilon to account for floating-point precision issues if value.is_nan() || value.is_infinite() { - anyhow::bail!("{FAILED} invalid f64 for '{param}', was {value}") + anyhow::bail!("invalid f64 for '{param}', was {value}") } if value < l - EPSILON || value > r + EPSILON { - anyhow::bail!("{FAILED} invalid f64 for '{param}' not in range [{l}, {r}], was {value}") + anyhow::bail!("invalid f64 for '{param}' not in range [{l}, {r}], was {value}") } Ok(()) } @@ -214,7 +213,7 @@ pub fn check_in_range_inclusive_usize( param: &str, ) -> anyhow::Result<()> { if value < l || value > r { - anyhow::bail!("{FAILED} invalid usize for '{param}' not in range [{l}, {r}], was {value}") + anyhow::bail!("invalid usize for '{param}' not in range [{l}, {r}], was {value}") } Ok(()) } @@ -223,7 +222,7 @@ pub fn check_in_range_inclusive_usize( pub fn check_slice_empty(slice: &[T], param: &str) -> anyhow::Result<()> { if !slice.is_empty() { anyhow::bail!( - "{FAILED} the '{param}' slice `&[{}]` was not empty", + "the '{param}' slice `&[{}]` was not empty", std::any::type_name::() ) } @@ -234,7 +233,7 @@ pub fn check_slice_empty(slice: &[T], param: &str) -> anyhow::Result<()> { pub fn check_slice_not_empty(slice: &[T], param: &str) -> anyhow::Result<()> { if slice.is_empty() { anyhow::bail!( - "{FAILED} the '{param}' slice `&[{}]` was empty", + "the '{param}' slice `&[{}]` was empty", std::any::type_name::() ) } @@ -245,7 +244,7 @@ pub fn check_slice_not_empty(slice: &[T], param: &str) -> anyhow::Result<()> pub fn check_map_empty(map: &HashMap, param: &str) -> anyhow::Result<()> { if !map.is_empty() { anyhow::bail!( - "{FAILED} the '{param}' map `&<{}, {}>` was not empty", + "the '{param}' map `&<{}, {}>` was not empty", std::any::type_name::(), std::any::type_name::(), ) @@ -257,7 +256,7 @@ pub fn check_map_empty(map: &HashMap, param: &str) -> anyhow::Result pub fn check_map_not_empty(map: &HashMap, param: &str) -> anyhow::Result<()> { if map.is_empty() { anyhow::bail!( - "{FAILED} the '{param}' map `&<{}, {}>` was empty", + "the '{param}' map `&<{}, {}>` was empty", std::any::type_name::(), std::any::type_name::(), ) @@ -279,7 +278,7 @@ where { if map.contains_key(key) { anyhow::bail!( - "{FAILED} the '{key_name}' key {key} was already in the '{map_name}' map `&<{}, {}>`", + "the '{key_name}' key {key} was already in the '{map_name}' map `&<{}, {}>`", std::any::type_name::(), std::any::type_name::(), ) @@ -301,7 +300,7 @@ where { if !map.contains_key(key) { anyhow::bail!( - "{FAILED} the '{key_name}' key {key} was not in the '{map_name}' map `&<{}, {}>`", + "the '{key_name}' key {key} was not in the '{map_name}' map `&<{}, {}>`", std::any::type_name::(), std::any::type_name::(), ) @@ -323,7 +322,7 @@ where { if set.contains(member) { anyhow::bail!( - "{FAILED} the '{member_name}' member was already in the '{set_name}' set `&<{}>`", + "the '{member_name}' member was already in the '{set_name}' set `&<{}>`", std::any::type_name::(), ) } @@ -344,7 +343,7 @@ where { if !set.contains(member) { anyhow::bail!( - "{FAILED} the '{member_name}' member was not in the '{set_name}' set `&<{}>`", + "the '{member_name}' member was not in the '{set_name}' set `&<{}>`", std::any::type_name::(), ) } diff --git a/nautilus_core/core/src/nanos.rs b/nautilus_core/core/src/nanos.rs index 442ed16be7ff..c6ce3f51c58b 100644 --- a/nautilus_core/core/src/nanos.rs +++ b/nautilus_core/core/src/nanos.rs @@ -100,7 +100,11 @@ impl From for u64 { impl From<&str> for UnixNanos { fn from(value: &str) -> Self { - Self(value.parse().unwrap()) + Self( + value + .parse() + .expect("Value is not a valid u64 string representation"), + ) } } diff --git a/nautilus_core/core/src/uuid.rs b/nautilus_core/core/src/uuid.rs index 3e9ede04fc7c..7ef46edbbf54 100644 --- a/nautilus_core/core/src/uuid.rs +++ b/nautilus_core/core/src/uuid.rs @@ -47,7 +47,8 @@ impl UUID4 { #[must_use] pub fn new() -> Self { let uuid = Uuid::new_v4(); - let c_string = CString::new(uuid.to_string()).expect("`CString` conversion failed"); + let c_string = + CString::new(uuid.to_string()).expect("Expected UUID to convert to valid `CString`"); let bytes = c_string.as_bytes_with_nul(); let mut value = [0; UUID4_LEN]; value[..bytes.len()].copy_from_slice(bytes); @@ -59,7 +60,8 @@ impl UUID4 { #[must_use] pub fn to_cstr(&self) -> &CStr { // SAFETY: We always store valid C strings - CStr::from_bytes_with_nul(&self.value).unwrap() + CStr::from_bytes_with_nul(&self.value) + .expect("Expected UUID byte representation to be a valid `CString`") } } @@ -79,7 +81,7 @@ impl FromStr for UUID4 { impl From<&str> for UUID4 { fn from(input: &str) -> Self { - input.parse().unwrap_or_else(|err| panic!("{}", err)) + input.parse().expect("Input should be a valid UUID") } } diff --git a/nautilus_core/data/src/aggregation.rs b/nautilus_core/data/src/aggregation.rs index 1fdff14087cf..65c0ebe727fc 100644 --- a/nautilus_core/data/src/aggregation.rs +++ b/nautilus_core/data/src/aggregation.rs @@ -23,7 +23,10 @@ use std::ops::Add; use chrono::TimeDelta; use nautilus_common::{clock::Clock, timer::TimeEvent}; -use nautilus_core::{correctness, nanos::UnixNanos}; +use nautilus_core::{ + correctness::{self, FAILED}, + nanos::UnixNanos, +}; use nautilus_model::{ data::{ bar::{get_bar_interval, get_bar_interval_ns, get_time_bar_start, Bar, BarType}, @@ -85,14 +88,14 @@ impl BarBuilder { "instrument.id", "bar_type.instrument_id", ) - .unwrap(); + .expect(FAILED); correctness::check_equal( bar_type.aggregation_source, AggregationSource::Internal, "bar_type.aggregation_source", "AggregationSource::Internal", ) - .unwrap(); + .expect(FAILED); Self { bar_type, @@ -345,7 +348,7 @@ impl BarAggregator for VolumeBarAggregator { if self.core.builder.volume.raw + raw_size_update < raw_step { self.core.apply_update( price, - Quantity::from_raw(raw_size_update, size.precision).unwrap(), + Quantity::from_raw(raw_size_update, size.precision), ts_event, ); break; @@ -354,7 +357,7 @@ impl BarAggregator for VolumeBarAggregator { raw_size_diff = raw_step - self.core.builder.volume.raw; self.core.apply_update( price, - Quantity::from_raw(raw_size_update, size.precision).unwrap(), + Quantity::from_raw(raw_size_update, size.precision), ts_event, ); @@ -412,21 +415,15 @@ impl BarAggregator for ValueBarAggregator { let value_update = price.as_f64() * size_update; if self.cum_value + value_update < self.core.bar_type.spec.step as f64 { self.cum_value += value_update; - self.core.apply_update( - price, - Quantity::new(size_update, size.precision).unwrap(), - ts_event, - ); + self.core + .apply_update(price, Quantity::new(size_update, size.precision), ts_event); break; } let value_diff = self.core.bar_type.spec.step as f64 - self.cum_value; let size_diff = size_update * (value_diff / value_update); - self.core.apply_update( - price, - Quantity::new(size_diff, size.precision).unwrap(), - ts_event, - ); + self.core + .apply_update(price, Quantity::new(size_diff, size.precision), ts_event); self.core.build_now_and_send(); self.cum_value = 0.0; @@ -624,11 +621,11 @@ mod tests { let partial_bar = Bar::new( bar_type, - Price::new(1.00001, 8).unwrap(), - Price::new(1.00010, 8).unwrap(), - Price::new(1.00000, 8).unwrap(), - Price::new(1.00002, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00001, 8), + Price::new(1.00010, 8), + Price::new(1.00000, 8), + Price::new(1.00002, 8), + Quantity::new(1.0, 0), UnixNanos::from(1_000_000_000), UnixNanos::from(2_000_000_000), ) @@ -637,11 +634,11 @@ mod tests { builder.set_partial(partial_bar); let bar = builder.build_now(); - assert_eq!(bar.open, Price::new(1.00001, 8).unwrap()); - assert_eq!(bar.high, Price::new(1.00010, 8).unwrap()); - assert_eq!(bar.low, Price::new(1.00000, 8).unwrap()); - assert_eq!(bar.close, Price::new(1.00002, 8).unwrap()); - assert_eq!(bar.volume, Quantity::new(1.0, 0).unwrap()); + assert_eq!(bar.open, Price::new(1.00001, 8)); + assert_eq!(bar.high, Price::new(1.00010, 8)); + assert_eq!(bar.low, Price::new(1.00000, 8)); + assert_eq!(bar.close, Price::new(1.00002, 8)); + assert_eq!(bar.volume, Quantity::new(1.0, 0)); assert_eq!(bar.ts_init, 2_000_000_000); assert_eq!(builder.ts_last, 2_000_000_000); } @@ -658,11 +655,11 @@ mod tests { let partial_bar1 = Bar::new( bar_type, - Price::new(1.00001, 8).unwrap(), - Price::new(1.00010, 8).unwrap(), - Price::new(1.00000, 8).unwrap(), - Price::new(1.00002, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00001, 8), + Price::new(1.00010, 8), + Price::new(1.00000, 8), + Price::new(1.00002, 8), + Quantity::new(1.0, 0), UnixNanos::from(1_000_000_000), UnixNanos::from(1_000_000_000), ) @@ -670,11 +667,11 @@ mod tests { let partial_bar2 = Bar::new( bar_type, - Price::new(2.00001, 8).unwrap(), - Price::new(2.00010, 8).unwrap(), - Price::new(2.00000, 8).unwrap(), - Price::new(2.00002, 8).unwrap(), - Quantity::new(2.0, 0).unwrap(), + Price::new(2.00001, 8), + Price::new(2.00010, 8), + Price::new(2.00000, 8), + Price::new(2.00002, 8), + Quantity::new(2.0, 0), UnixNanos::from(3_000_000_000), UnixNanos::from(3_000_000_000), ) @@ -687,11 +684,11 @@ mod tests { UnixNanos::from(4_000_000_000), ); - assert_eq!(bar.open, Price::new(1.00001, 8).unwrap()); - assert_eq!(bar.high, Price::new(1.00010, 8).unwrap()); - assert_eq!(bar.low, Price::new(1.00000, 8).unwrap()); - assert_eq!(bar.close, Price::new(1.00002, 8).unwrap()); - assert_eq!(bar.volume, Quantity::new(1.0, 0).unwrap()); + assert_eq!(bar.open, Price::new(1.00001, 8)); + assert_eq!(bar.high, Price::new(1.00010, 8)); + assert_eq!(bar.low, Price::new(1.00000, 8)); + assert_eq!(bar.close, Price::new(1.00002, 8)); + assert_eq!(bar.volume, Quantity::new(1.0, 0)); assert_eq!(bar.ts_init, 4_000_000_000); assert_eq!(builder.ts_last, 1_000_000_000); } @@ -707,8 +704,8 @@ mod tests { let mut builder = BarBuilder::new(&instrument, bar_type); builder.update( - Price::new(1.00000, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00000, 8), + Quantity::new(1.0, 0), UnixNanos::from(0), ); @@ -730,13 +727,13 @@ mod tests { let mut builder = BarBuilder::new(&instrument, bar_type); builder.update( - Price::new(1.00000, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00000, 8), + Quantity::new(1.0, 0), UnixNanos::from(1_000), ); builder.update( - Price::new(1.00001, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00001, 8), + Quantity::new(1.0, 0), UnixNanos::from(500), ); @@ -757,8 +754,8 @@ mod tests { for _ in 0..5 { builder.update( - Price::new(1.00000, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00000, 8), + Quantity::new(1.0, 0), UnixNanos::from(1_000), ); } @@ -794,28 +791,28 @@ mod tests { let mut builder = BarBuilder::new(&instrument, bar_type); builder.update( - Price::new(1.00001, 8).unwrap(), - Quantity::new(2.0, 0).unwrap(), + Price::new(1.00001, 8), + Quantity::new(2.0, 0), UnixNanos::from(0), ); builder.update( - Price::new(1.00002, 8).unwrap(), - Quantity::new(2.0, 0).unwrap(), + Price::new(1.00002, 8), + Quantity::new(2.0, 0), UnixNanos::from(0), ); builder.update( - Price::new(1.00000, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00000, 8), + Quantity::new(1.0, 0), UnixNanos::from(1_000_000_000), ); let bar = builder.build_now(); - assert_eq!(bar.open, Price::new(1.00001, 8).unwrap()); - assert_eq!(bar.high, Price::new(1.00002, 8).unwrap()); - assert_eq!(bar.low, Price::new(1.00000, 8).unwrap()); - assert_eq!(bar.close, Price::new(1.00000, 8).unwrap()); - assert_eq!(bar.volume, Quantity::new(5.0, 0).unwrap()); + assert_eq!(bar.open, Price::new(1.00001, 8)); + assert_eq!(bar.high, Price::new(1.00002, 8)); + assert_eq!(bar.low, Price::new(1.00000, 8)); + assert_eq!(bar.close, Price::new(1.00000, 8)); + assert_eq!(bar.volume, Quantity::new(5.0, 0)); assert_eq!(bar.ts_init, 1_000_000_000); assert_eq!(builder.ts_last, 1_000_000_000); assert_eq!(builder.count, 0); @@ -832,35 +829,35 @@ mod tests { let mut builder = BarBuilder::new(&instrument, bar_type); builder.update( - Price::new(1.00001, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00001, 8), + Quantity::new(1.0, 0), UnixNanos::from(0), ); builder.build_now(); // This close should become the next open builder.update( - Price::new(1.00000, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00000, 8), + Quantity::new(1.0, 0), UnixNanos::from(0), ); builder.update( - Price::new(1.00003, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00003, 8), + Quantity::new(1.0, 0), UnixNanos::from(0), ); builder.update( - Price::new(1.00002, 8).unwrap(), - Quantity::new(1.0, 0).unwrap(), + Price::new(1.00002, 8), + Quantity::new(1.0, 0), UnixNanos::from(0), ); let bar = builder.build_now(); - assert_eq!(bar.open, Price::new(1.00000, 8).unwrap()); - assert_eq!(bar.high, Price::new(1.00003, 8).unwrap()); - assert_eq!(bar.low, Price::new(1.00000, 8).unwrap()); - assert_eq!(bar.close, Price::new(1.00002, 8).unwrap()); - assert_eq!(bar.volume, Quantity::new(3.0, 0).unwrap()); + assert_eq!(bar.open, Price::new(1.00000, 8)); + assert_eq!(bar.high, Price::new(1.00003, 8)); + assert_eq!(bar.low, Price::new(1.00000, 8)); + assert_eq!(bar.close, Price::new(1.00002, 8)); + assert_eq!(bar.volume, Quantity::new(3.0, 0)); } // #[rstest] @@ -875,10 +872,10 @@ mod tests { // // let tick = QuoteTick::new( // instrument.id(), - // Price::new(1.00001, 8).unwrap(), - // Price::new(1.00004, 8).unwrap(), - // Quantity::new(1.0, 0).unwrap(), - // Quantity::new(1.0, 0).unwrap(), + // Price::new(1.00001, 8), + // Price::new(1.00004, 8), + // Quantity::new(1.0, 0), + // Quantity::new(1.0, 0), // UnixNanos::from(0), // UnixNanos::from(0), // ); @@ -902,8 +899,8 @@ mod tests { // // let tick = TradeTick::new( // instrument.id(), - // Price::new(1.00001, 8).unwrap(), - // Quantity::new(1.0, 0).unwrap(), + // Price::new(1.00001, 8), + // Quantity::new(1.0, 0), // AggressorSide::Buyer, // TradeId::new("123456"), // UnixNanos::from(0), @@ -928,30 +925,30 @@ mod tests { // // let tick1 = QuoteTick::new( // instrument.id(), - // Price::new(1.00001, 8).unwrap(), - // Price::new(1.00004, 8).unwrap(), - // Quantity::new(1.0, 0).unwrap(), - // Quantity::new(1.0, 0).unwrap(), + // Price::new(1.00001, 8), + // Price::new(1.00004, 8), + // Quantity::new(1.0, 0), + // Quantity::new(1.0, 0), // UnixNanos::from(0), // UnixNanos::from(0), // ); // // let tick2 = QuoteTick::new( // instrument.id(), - // Price::new(1.00002, 8).unwrap(), - // Price::new(1.00005, 8).unwrap(), - // Quantity::new(1.0, 0).unwrap(), - // Quantity::new(1.0, 0).unwrap(), + // Price::new(1.00002, 8), + // Price::new(1.00005, 8), + // Quantity::new(1.0, 0), + // Quantity::new(1.0, 0), // UnixNanos::from(0), // UnixNanos::from(0), // ); // // let tick3 = QuoteTick::new( // instrument.id(), - // Price::new(1.00000, 8).unwrap(), - // Price::new(1.00003, 8).unwrap(), - // Quantity::new(1.0, 0).unwrap(), - // Quantity::new(1.0, 0).unwrap(), + // Price::new(1.00000, 8), + // Price::new(1.00003, 8), + // Quantity::new(1.0, 0), + // Quantity::new(1.0, 0), // UnixNanos::from(0), // UnixNanos::from(0), // ); @@ -963,10 +960,10 @@ mod tests { // let handler_guard = handler.lock().unwrap(); // assert_eq!(handler_guard.len(), 1); // let bar = &handler_guard[0]; - // assert_eq!(bar.open, Price::new(1.000025, 8).unwrap()); - // assert_eq!(bar.high, Price::new(1.000035, 8).unwrap()); - // assert_eq!(bar.low, Price::new(1.000015, 8).unwrap()); - // assert_eq!(bar.close, Price::new(1.000015, 8).unwrap()); - // assert_eq!(bar.volume, Quantity::new(3.0, 0).unwrap()); + // assert_eq!(bar.open, Price::new(1.000025, 8)); + // assert_eq!(bar.high, Price::new(1.000035, 8)); + // assert_eq!(bar.low, Price::new(1.000015, 8)); + // assert_eq!(bar.close, Price::new(1.000015, 8)); + // assert_eq!(bar.volume, Quantity::new(3.0, 0)); // } } diff --git a/nautilus_core/indicators/src/stubs.rs b/nautilus_core/indicators/src/stubs.rs index c5d5f860cdf7..5b5e959a0ba8 100644 --- a/nautilus_core/indicators/src/stubs.rs +++ b/nautilus_core/indicators/src/stubs.rs @@ -84,8 +84,8 @@ pub fn trade_tick() -> TradeTick { #[fixture] pub fn bar_ethusdt_binance_minute_bid(#[default("1522")] close_price: &str) -> Bar { let instrument_id = InstrumentId { - symbol: Symbol::new("ETHUSDT-PERP.BINANCE").unwrap(), - venue: Venue::new("BINANCE").unwrap(), + symbol: Symbol::new("ETHUSDT-PERP.BINANCE"), + venue: Venue::new("BINANCE"), }; let bar_spec = BarSpecification { step: 1, diff --git a/nautilus_core/infrastructure/src/redis/cache.rs b/nautilus_core/infrastructure/src/redis/cache.rs index a16e9a236164..d2200d676152 100644 --- a/nautilus_core/infrastructure/src/redis/cache.rs +++ b/nautilus_core/infrastructure/src/redis/cache.rs @@ -377,7 +377,7 @@ fn insert( key: &str, value: Vec, ) -> anyhow::Result<()> { - check_slice_not_empty(value.as_slice(), stringify!(value))?; + check_slice_not_empty(value.as_slice(), stringify!(value)).unwrap(); match collection { INDEX => insert_index(pipe, key, &value), @@ -502,7 +502,7 @@ fn update( key: &str, value: Vec, ) -> anyhow::Result<()> { - check_slice_not_empty(value.as_slice(), stringify!(value))?; + check_slice_not_empty(value.as_slice(), stringify!(value)).unwrap(); match collection { ACCOUNTS => { diff --git a/nautilus_core/infrastructure/src/sql/models/data.rs b/nautilus_core/infrastructure/src/sql/models/data.rs index 412049e4393e..ff6b7828ca0e 100644 --- a/nautilus_core/infrastructure/src/sql/models/data.rs +++ b/nautilus_core/infrastructure/src/sql/models/data.rs @@ -51,7 +51,7 @@ impl<'r> FromRow<'r, PgRow> for TradeTickModel { .map(|x| x.0)?; let trade_id = row .try_get::<&str, _>("venue_trade_id") - .map(|x| TradeId::from_str(x).unwrap())?; + .map(TradeId::from)?; let ts_event = row .try_get::("ts_event") .map(|res| UnixNanos::from(res.as_str()))?; diff --git a/nautilus_core/infrastructure/src/sql/models/instruments.rs b/nautilus_core/infrastructure/src/sql/models/instruments.rs index 7c91e38a7733..9230fccc2674 100644 --- a/nautilus_core/infrastructure/src/sql/models/instruments.rs +++ b/nautilus_core/infrastructure/src/sql/models/instruments.rs @@ -193,8 +193,7 @@ impl<'r> FromRow<'r, PgRow> for CryptoFutureModel { min_price, ts_event, ts_init, - ) - .unwrap(); + ); Ok(CryptoFutureModel(inst)) } } @@ -295,8 +294,7 @@ impl<'r> FromRow<'r, PgRow> for CryptoPerpetualModel { min_price, ts_event, ts_init, - ) - .unwrap(); + ); Ok(CryptoPerpetualModel(inst)) } } @@ -392,8 +390,7 @@ impl<'r> FromRow<'r, PgRow> for CurrencyPairModel { min_price, ts_event, ts_init, - ) - .unwrap(); + ); Ok(CurrencyPairModel(inst)) } } @@ -472,8 +469,7 @@ impl<'r> FromRow<'r, PgRow> for EquityModel { min_price, ts_event, ts_init, - ) - .unwrap(); + ); Ok(EquityModel(inst)) } } @@ -485,7 +481,7 @@ impl<'r> FromRow<'r, PgRow> for FuturesContractModel { .map(|res| InstrumentId::from(res.as_str()))?; let raw_symbol = row .try_get::("raw_symbol") - .map(|res| Symbol::new(res.as_str()).unwrap())?; + .map(|res| Symbol::new(res.as_str()))?; let asset_class = row .try_get::("asset_class") .map(|res| res.0)?; @@ -564,8 +560,7 @@ impl<'r> FromRow<'r, PgRow> for FuturesContractModel { Some(margin_maint), ts_event, ts_init, - ) - .unwrap(); + ); Ok(FuturesContractModel(inst)) } } @@ -583,7 +578,7 @@ impl<'r> FromRow<'r, PgRow> for OptionsContractModel { .map(|res| InstrumentId::from(res.as_str()))?; let raw_symbol = row .try_get::("raw_symbol") - .map(|res| Symbol::new(res.as_str()).unwrap())?; + .map(|res| Symbol::new(res.as_str()))?; let asset_class = row .try_get::("asset_class") .map(|res| res.0)?; @@ -671,8 +666,7 @@ impl<'r> FromRow<'r, PgRow> for OptionsContractModel { Some(margin_maint), ts_event, ts_init, - ) - .unwrap(); + ); Ok(OptionsContractModel(inst)) } } diff --git a/nautilus_core/infrastructure/src/sql/models/types.rs b/nautilus_core/infrastructure/src/sql/models/types.rs index 05674e09e199..e108b4a6f58b 100644 --- a/nautilus_core/infrastructure/src/sql/models/types.rs +++ b/nautilus_core/infrastructure/src/sql/models/types.rs @@ -33,8 +33,7 @@ impl<'r> FromRow<'r, PgRow> for CurrencyModel { iso4217 as u16, name.as_str(), currency_type_model.0, - ) - .unwrap(); + ); Ok(CurrencyModel(currency)) } } diff --git a/nautilus_core/infrastructure/tests/test_cache_database_postgres.rs b/nautilus_core/infrastructure/tests/test_cache_database_postgres.rs index 8030e4b215a0..00dc9f577662 100644 --- a/nautilus_core/infrastructure/tests/test_cache_database_postgres.rs +++ b/nautilus_core/infrastructure/tests/test_cache_database_postgres.rs @@ -76,10 +76,10 @@ mod serial_tests { // 1. first define and add currencies as they are contain foreign keys for instruments let mut pg_cache = get_pg_cache_database().await.unwrap(); // Define currencies - let btc = Currency::new("BTC", 8, 0, "BTC", CurrencyType::Crypto).unwrap(); - let eth = Currency::new("ETH", 2, 0, "ETH", CurrencyType::Crypto).unwrap(); - let usd = Currency::new("USD", 2, 0, "USD", CurrencyType::Fiat).unwrap(); - let usdt = Currency::new("USDT", 2, 0, "USDT", CurrencyType::Crypto).unwrap(); + let btc = Currency::new("BTC", 8, 0, "BTC", CurrencyType::Crypto); + let eth = Currency::new("ETH", 2, 0, "ETH", CurrencyType::Crypto); + let usd = Currency::new("USD", 2, 0, "USD", CurrencyType::Fiat); + let usdt = Currency::new("USDT", 2, 0, "USDT", CurrencyType::Crypto); // Insert all the currencies pg_cache.add_currency(&btc).unwrap(); pg_cache.add_currency(ð).unwrap(); @@ -214,8 +214,8 @@ mod serial_tests { #[tokio::test(flavor = "multi_thread")] async fn test_postgres_cache_database_add_order_and_load_indexes() { - let client_order_id_1 = ClientOrderId::new("O-19700101-000000-001-001-1").unwrap(); - let client_order_id_2 = ClientOrderId::new("O-19700101-000000-001-001-2").unwrap(); + let client_order_id_1 = ClientOrderId::new("O-19700101-000000-001-001-1"); + let client_order_id_2 = ClientOrderId::new("O-19700101-000000-001-001-2"); let instrument = currency_pair_ethusdt(); let mut pg_cache = get_pg_cache_database().await.unwrap(); let market_order = TestOrderStubs::market_order( @@ -242,7 +242,7 @@ mod serial_tests { .add_instrument(&InstrumentAny::CurrencyPair(instrument)) .unwrap(); // Set client id - let client_id = ClientId::new("TEST").unwrap(); + let client_id = ClientId::new("TEST"); // add orders pg_cache.add_order(&market_order, Some(client_id)).unwrap(); pg_cache.add_order(&limit_order, Some(client_id)).unwrap(); @@ -288,7 +288,7 @@ mod serial_tests { #[tokio::test(flavor = "multi_thread")] async fn test_update_order_for_open_order() { - let client_order_id_1 = ClientOrderId::new("O-19700101-000000-001-002-1").unwrap(); + let client_order_id_1 = ClientOrderId::new("O-19700101-000000-001-002-1"); let instrument = InstrumentAny::CurrencyPair(currency_pair_ethusdt()); let account = account_id(); let mut pg_cache = get_pg_cache_database().await.unwrap(); @@ -311,25 +311,22 @@ mod serial_tests { market_order.apply(submitted).unwrap(); pg_cache.update_order(&market_order).unwrap(); - let accepted = TestOrderEventStubs::order_accepted( - &market_order, - account, - VenueOrderId::new("001").unwrap(), - ); + let accepted = + TestOrderEventStubs::order_accepted(&market_order, account, VenueOrderId::new("001")); market_order.apply(accepted).unwrap(); pg_cache.update_order(&market_order).unwrap(); let filled = TestOrderEventStubs::order_filled( &market_order, &instrument, - Some(TradeId::new("T-19700101-000000-001-001-1").unwrap()), + Some(TradeId::new("T-19700101-000000-001-001-1")), None, Some(Price::from("100.0")), Some(Quantity::from("1.0")), None, None, None, - Some(AccountId::new("SIM-001").unwrap()), + Some(AccountId::new("SIM-001")), ); market_order.apply(filled).unwrap(); pg_cache.update_order(&market_order).unwrap(); diff --git a/nautilus_core/infrastructure/tests/test_cache_postgres.rs b/nautilus_core/infrastructure/tests/test_cache_postgres.rs index c5304dafea70..539bc18a41f8 100644 --- a/nautilus_core/infrastructure/tests/test_cache_postgres.rs +++ b/nautilus_core/infrastructure/tests/test_cache_postgres.rs @@ -46,8 +46,8 @@ mod serial_tests { async fn test_cache_instruments() { let mut database = get_pg_cache_database().await.unwrap(); let mut cache = get_cache(Some(Box::new(database.clone()))); - let eth = Currency::new("ETH", 2, 0, "ETH", CurrencyType::Crypto).unwrap(); - let usdt = Currency::new("USDT", 2, 0, "USDT", CurrencyType::Crypto).unwrap(); + let eth = Currency::new("ETH", 2, 0, "ETH", CurrencyType::Crypto); + let usdt = Currency::new("USDT", 2, 0, "USDT", CurrencyType::Crypto); let crypto_perpetual = InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt()); // insert into database and wait database.add_currency(ð).unwrap(); @@ -81,7 +81,7 @@ mod serial_tests { instrument.id(), OrderSide::Buy, Quantity::from("1.0"), - Some(ClientOrderId::new("O-19700101-0000-001-001-1").unwrap()), + Some(ClientOrderId::new("O-19700101-0000-001-001-1")), None, ); // add foreign key dependencies: instrument and currencies diff --git a/nautilus_core/model/src/accounts/base.rs b/nautilus_core/model/src/accounts/base.rs index aaf3b864e8cd..7efae9bd5bee 100644 --- a/nautilus_core/model/src/accounts/base.rs +++ b/nautilus_core/model/src/accounts/base.rs @@ -165,11 +165,11 @@ impl BaseAccount { // Handle inverse if instrument.is_inverse() && !use_quote_for_inverse.unwrap_or(false) { - Ok(Money::new(locked, base_currency).unwrap()) + Ok(Money::new(locked, base_currency)) } else if side == OrderSide::Buy { - Ok(Money::new(locked, quote_currency).unwrap()) + Ok(Money::new(locked, quote_currency)) } else if side == OrderSide::Sell { - Ok(Money::new(locked, base_currency).unwrap()) + Ok(Money::new(locked, base_currency)) } else { panic!("Invalid `OrderSide` in `base_calculate_balance_locked`") } @@ -193,23 +193,23 @@ impl BaseAccount { if let (Some(base_currency_value), None) = (base_currency, self.base_currency) { pnls.insert( base_currency_value, - Money::new(fill_qty, base_currency_value).unwrap(), + Money::new(fill_qty, base_currency_value), ); } pnls.insert( quote_currency, - Money::new(-(fill_qty * fill_px), quote_currency).unwrap(), + Money::new(-(fill_qty * fill_px), quote_currency), ); } else if fill.order_side == OrderSide::Sell { if let (Some(base_currency_value), None) = (base_currency, self.base_currency) { pnls.insert( base_currency_value, - Money::new(-fill_qty, base_currency_value).unwrap(), + Money::new(-fill_qty, base_currency_value), ); } pnls.insert( quote_currency, - Money::new(fill_qty * fill_px, quote_currency).unwrap(), + Money::new(fill_qty * fill_px, quote_currency), ); } else { panic!("Invalid `OrderSide` in base_calculate_pnls") @@ -240,9 +240,9 @@ impl BaseAccount { panic!("Invalid `LiquiditySide` {liquidity_side}") }; if instrument.is_inverse() && !use_quote_for_inverse.unwrap_or(false) { - Ok(Money::new(commission, instrument.base_currency().unwrap()).unwrap()) + Ok(Money::new(commission, instrument.base_currency().unwrap())) } else { - Ok(Money::new(commission, instrument.quote_currency()).unwrap()) + Ok(Money::new(commission, instrument.quote_currency())) } } } diff --git a/nautilus_core/model/src/accounts/cash.rs b/nautilus_core/model/src/accounts/cash.rs index cc2170e32aa4..937c28bb809d 100644 --- a/nautilus_core/model/src/accounts/cash.rs +++ b/nautilus_core/model/src/accounts/cash.rs @@ -230,8 +230,7 @@ impl Default for CashAccount { Money::from("1000000 USD"), Money::from("0 USD"), Money::from("1000000 USD"), - ) - .unwrap()], + )], vec![], true, uuid4(), @@ -478,7 +477,7 @@ mod tests { &order, &audusd_sim, None, - Some(PositionId::new("P-123456").unwrap()), + Some(PositionId::new("P-123456")), Some(Price::from("0.8")), None, None, @@ -486,7 +485,7 @@ mod tests { None, Some(AccountId::from("SIM-001")), ); - let position = Position::new(&audusd_sim, fill.clone().into()).unwrap(); + let position = Position::new(&audusd_sim, fill.clone().into()); let pnls = cash_account_million_usd .calculate_pnls(audusd_sim, fill.into(), Some(position)) // TODO: Remove clone .unwrap(); @@ -510,7 +509,7 @@ mod tests { &order1, &btcusdt, None, - Some(PositionId::new("P-123456").unwrap()), + Some(PositionId::new("P-123456")), Some(Price::from("45500.00")), None, None, @@ -518,7 +517,7 @@ mod tests { None, Some(AccountId::from("SIM-001")), ); - let position = Position::new(&btcusdt, fill1.clone().into()).unwrap(); + let position = Position::new(&btcusdt, fill1.clone().into()); let result1 = cash_account_multi .calculate_pnls( currency_pair_btcusdt.into_any(), @@ -537,7 +536,7 @@ mod tests { &order2, &btcusdt, None, - Some(PositionId::new("P-123456").unwrap()), + Some(PositionId::new("P-123456")), Some(Price::from("45500.00")), None, None, diff --git a/nautilus_core/model/src/accounts/margin.rs b/nautilus_core/model/src/accounts/margin.rs index 746691ad5fb3..f33088b58c02 100644 --- a/nautilus_core/model/src/accounts/margin.rs +++ b/nautilus_core/model/src/accounts/margin.rs @@ -119,10 +119,9 @@ impl MarginAccount { instrument_id, MarginBalance::new( margin_init, - Money::new(0.0, margin_init.currency).unwrap(), + Money::new(0.0, margin_init.currency), instrument_id, - ) - .unwrap(), + ), ); } else { // update the margin_balance initial property with margin_init @@ -153,11 +152,10 @@ impl MarginAccount { self.margins.insert( instrument_id, MarginBalance::new( - Money::new(0.0, margin_maintenance.currency).unwrap(), + Money::new(0.0, margin_maintenance.currency), margin_maintenance, instrument_id, - ) - .unwrap(), + ), ); } else { // update the margin_balance maintenance property with margin_maintenance @@ -198,9 +196,9 @@ impl MarginAccount { margin += adjusted_notional * instrument.taker_fee().to_f64().unwrap() * 2.0; let use_quote_for_inverse = use_quote_for_inverse.unwrap_or(false); if instrument.is_inverse() && !use_quote_for_inverse { - Money::new(margin, instrument.base_currency().unwrap()).unwrap() + Money::new(margin, instrument.base_currency().unwrap()) } else { - Money::new(margin, instrument.quote_currency()).unwrap() + Money::new(margin, instrument.quote_currency()) } } @@ -224,9 +222,9 @@ impl MarginAccount { margin += adjusted_notional * instrument.taker_fee().to_f64().unwrap(); let use_quote_for_inverse = use_quote_for_inverse.unwrap_or(false); if instrument.is_inverse() && !use_quote_for_inverse { - Money::new(margin, instrument.base_currency().unwrap()).unwrap() + Money::new(margin, instrument.base_currency().unwrap()) } else { - Money::new(margin, instrument.quote_currency()).unwrap() + Money::new(margin, instrument.quote_currency()) } } @@ -254,8 +252,7 @@ impl MarginAccount { current_balance.total, Money::from_raw(total_margin, currency), Money::from_raw(total_free, currency), - ) - .unwrap(); + ); self.balances.insert(currency, new_balance); } } diff --git a/nautilus_core/model/src/data/bar.rs b/nautilus_core/model/src/data/bar.rs index 61f0aac2935b..f0f34ef095e9 100644 --- a/nautilus_core/model/src/data/bar.rs +++ b/nautilus_core/model/src/data/bar.rs @@ -25,7 +25,7 @@ use std::{ use chrono::{DateTime, Datelike, TimeDelta, Timelike, Utc}; use derive_builder::Builder; use indexmap::IndexMap; -use nautilus_core::{nanos::UnixNanos, serialization::Serializable}; +use nautilus_core::{correctness::FAILED, nanos::UnixNanos, serialization::Serializable}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use super::GetTsInit; @@ -236,11 +236,10 @@ impl FromStr for BarType { } impl From<&str> for BarType { - fn from(input: &str) -> Self { - Self::from_str(input).unwrap() + fn from(value: &str) -> Self { + Self::from_str(value).expect(FAILED) } } - impl Display for BarType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( @@ -557,7 +556,7 @@ mod tests { #[rstest] fn test_bar_type_parse_valid() { let input = "BTCUSDT-PERP.BINANCE-1-MINUTE-LAST-EXTERNAL"; - let bar_type = BarType::from_str(input).unwrap(); + let bar_type = BarType::from(input); assert_eq!( bar_type.instrument_id, @@ -642,12 +641,12 @@ mod tests { #[rstest] fn test_bar_type_equality() { let instrument_id1 = InstrumentId { - symbol: Symbol::new("AUD/USD").unwrap(), - venue: Venue::new("SIM").unwrap(), + symbol: Symbol::new("AUD/USD"), + venue: Venue::new("SIM"), }; let instrument_id2 = InstrumentId { - symbol: Symbol::new("GBP/USD").unwrap(), - venue: Venue::new("SIM").unwrap(), + symbol: Symbol::new("GBP/USD"), + venue: Venue::new("SIM"), }; let bar_spec = BarSpecification { step: 1, @@ -677,13 +676,13 @@ mod tests { #[rstest] fn test_bar_type_comparison() { let instrument_id1 = InstrumentId { - symbol: Symbol::new("AUD/USD").unwrap(), - venue: Venue::new("SIM").unwrap(), + symbol: Symbol::new("AUD/USD"), + venue: Venue::new("SIM"), }; let instrument_id2 = InstrumentId { - symbol: Symbol::new("GBP/USD").unwrap(), - venue: Venue::new("SIM").unwrap(), + symbol: Symbol::new("GBP/USD"), + venue: Venue::new("SIM"), }; let bar_spec = BarSpecification { step: 1, @@ -715,8 +714,8 @@ mod tests { #[rstest] fn test_bar_equality() { let instrument_id = InstrumentId { - symbol: Symbol::new("AUDUSD").unwrap(), - venue: Venue::new("SIM").unwrap(), + symbol: Symbol::new("AUDUSD"), + venue: Venue::new("SIM"), }; let bar_spec = BarSpecification { step: 1, diff --git a/nautilus_core/model/src/data/mod.rs b/nautilus_core/model/src/data/mod.rs index b99863808b1c..918799afe91b 100644 --- a/nautilus_core/model/src/data/mod.rs +++ b/nautilus_core/model/src/data/mod.rs @@ -203,7 +203,7 @@ impl DataType { pub fn parse_venue_from_metadata(&self) -> Option { let metadata = self.metadata.as_ref().expect("metadata was None"); let venue_str = metadata.get("venue")?; - Some(Venue::from_str(venue_str).expect("Invalid `Venue`")) + Some(Venue::from(venue_str.as_str())) } pub fn parse_bar_type_from_metadata(&self) -> BarType { @@ -480,7 +480,7 @@ mod tests { assert_eq!( data_type.parse_venue_from_metadata().unwrap(), - Venue::from_str(venue_str).unwrap() + Venue::new(venue_str) ); } diff --git a/nautilus_core/model/src/data/quote.rs b/nautilus_core/model/src/data/quote.rs index e9d880c4bd30..800659187a25 100644 --- a/nautilus_core/model/src/data/quote.rs +++ b/nautilus_core/model/src/data/quote.rs @@ -24,7 +24,11 @@ use std::{ use derive_builder::Builder; use indexmap::IndexMap; -use nautilus_core::{correctness::check_equal_u8, nanos::UnixNanos, serialization::Serializable}; +use nautilus_core::{ + correctness::{check_equal_u8, FAILED}, + nanos::UnixNanos, + serialization::Serializable, +}; use serde::{Deserialize, Serialize}; use super::GetTsInit; @@ -76,13 +80,15 @@ impl QuoteTick { ask_price.precision, "bid_price.precision", "ask_price.precision", - )?; + ) + .expect(FAILED); check_equal_u8( bid_size.precision, ask_size.precision, "bid_size.precision", "ask_size.precision", - )?; + ) + .expect(FAILED); Ok(Self { instrument_id, bid_price, @@ -129,8 +135,7 @@ impl QuoteTick { PriceType::Mid => Price::from_raw( (self.bid_price.raw + self.ask_price.raw) / 2, cmp::min(self.bid_price.precision + 1, FIXED_PRECISION), - ) - .unwrap(), // Already a valid `Price` + ), _ => panic!("Cannot extract with price type {price_type}"), } } @@ -143,8 +148,7 @@ impl QuoteTick { PriceType::Mid => Quantity::from_raw( (self.bid_size.raw + self.ask_size.raw) / 2, cmp::min(self.bid_size.precision + 1, FIXED_PRECISION), - ) - .unwrap(), // Already a valid `Quantity` + ), _ => panic!("Cannot extract with price type {price_type}"), } } diff --git a/nautilus_core/model/src/data/stubs.rs b/nautilus_core/model/src/data/stubs.rs index 17745e0d97f9..f7ff260ecde7 100644 --- a/nautilus_core/model/src/data/stubs.rs +++ b/nautilus_core/model/src/data/stubs.rs @@ -60,7 +60,7 @@ impl Default for TradeTick { price: Price::from("1.00000"), size: Quantity::from(100_000), aggressor_side: AggressorSide::Buyer, - trade_id: TradeId::new("123456789").unwrap(), + trade_id: TradeId::new("123456789"), ts_event: UnixNanos::default(), ts_init: UnixNanos::default(), } @@ -227,8 +227,8 @@ pub fn stub_depth10() -> OrderBookDepth10 { for i in 0..DEPTH10_LEN { let order = BookOrder::new( OrderSide::Buy, - Price::new(price, 2).unwrap(), - Quantity::new(quantity, 0).unwrap(), + Price::new(price, 2), + Quantity::new(quantity, 0), order_id, ); @@ -248,8 +248,8 @@ pub fn stub_depth10() -> OrderBookDepth10 { for i in 0..DEPTH10_LEN { let order = BookOrder::new( OrderSide::Sell, - Price::new(price, 2).unwrap(), - Quantity::new(quantity, 0).unwrap(), + Price::new(price, 2), + Quantity::new(quantity, 0), order_id, ); @@ -316,7 +316,7 @@ pub fn stub_trade_tick_ethusdt_buyer() -> TradeTick { price: Price::from("10000.0000"), size: Quantity::from("1.00000000"), aggressor_side: AggressorSide::Buyer, - trade_id: TradeId::new("123456789").unwrap(), + trade_id: TradeId::new("123456789"), ts_event: UnixNanos::default(), ts_init: UnixNanos::from(1), } @@ -325,8 +325,8 @@ pub fn stub_trade_tick_ethusdt_buyer() -> TradeTick { #[fixture] pub fn stub_bar() -> Bar { let instrument_id = InstrumentId { - symbol: Symbol::new("AUD/USD").unwrap(), - venue: Venue::new("SIM").unwrap(), + symbol: Symbol::new("AUD/USD"), + venue: Venue::new("SIM"), }; let bar_spec = BarSpecification { step: 1, diff --git a/nautilus_core/model/src/events/account/stubs.rs b/nautilus_core/model/src/events/account/stubs.rs index 464a375a5322..a8441a75fe99 100644 --- a/nautilus_core/model/src/events/account/stubs.rs +++ b/nautilus_core/model/src/events/account/stubs.rs @@ -53,10 +53,11 @@ pub fn cash_account_state_million_usd( AccountState::new( account_id(), AccountType::Cash, - vec![ - AccountBalance::new(Money::from(total), Money::from(locked), Money::from(free)) - .unwrap(), - ], + vec![AccountBalance::new( + Money::from(total), + Money::from(locked), + Money::from(free), + )], vec![], true, UUID4::new(), @@ -76,8 +77,7 @@ pub fn cash_account_state_million_usdt() -> AccountState { Money::from("1000000 USD"), Money::from("0 USD"), Money::from("1000000 USD"), - ) - .unwrap()], + )], vec![], true, uuid4(), @@ -94,14 +94,12 @@ pub fn cash_account_state_multi() -> AccountState { Money::from("10 BTC"), Money::from("0 BTC"), Money::from("10 BTC"), - ) - .unwrap(); + ); let eth_account_balance = AccountBalance::new( Money::from("20 ETH"), Money::from("0 ETH"), Money::from("20 ETH"), - ) - .unwrap(); + ); AccountState::new( account_id(), AccountType::Cash, @@ -122,14 +120,12 @@ pub fn cash_account_state_multi_changed_btc() -> AccountState { Money::from("9 BTC"), Money::from("0.5 BTC"), Money::from("8.5 BTC"), - ) - .unwrap(); + ); let eth_account_balance = AccountBalance::new( Money::from("20 ETH"), Money::from("0 ETH"), Money::from("20 ETH"), - ) - .unwrap(); + ); AccountState::new( account_id(), AccountType::Cash, diff --git a/nautilus_core/model/src/events/order/filled.rs b/nautilus_core/model/src/events/order/filled.rs index 59f44fe1829c..325503c4cb06 100644 --- a/nautilus_core/model/src/events/order/filled.rs +++ b/nautilus_core/model/src/events/order/filled.rs @@ -140,7 +140,7 @@ impl Default for OrderFilled { position_id: None, order_side: OrderSide::Buy, order_type: OrderType::Market, - last_qty: Quantity::new(100_000.0, 0).unwrap(), + last_qty: Quantity::new(100_000.0, 0), last_px: Price::from("1.00000"), currency: Currency::USD(), commission: None, diff --git a/nautilus_core/model/src/events/order/initialized.rs b/nautilus_core/model/src/events/order/initialized.rs index 0d06ff6c6b5d..01ae1babaf9a 100644 --- a/nautilus_core/model/src/events/order/initialized.rs +++ b/nautilus_core/model/src/events/order/initialized.rs @@ -91,7 +91,7 @@ impl Default for OrderInitialized { client_order_id: ClientOrderId::default(), order_side: OrderSide::Buy, order_type: OrderType::Market, - quantity: Quantity::new(100_000.0, 0).unwrap(), + quantity: Quantity::new(100_000.0, 0), price: Default::default(), trigger_price: Default::default(), trigger_type: Default::default(), diff --git a/nautilus_core/model/src/events/order/stubs.rs b/nautilus_core/model/src/events/order/stubs.rs index 132b65a6bfb2..1eb6d0990cf1 100644 --- a/nautilus_core/model/src/events/order/stubs.rs +++ b/nautilus_core/model/src/events/order/stubs.rs @@ -50,9 +50,9 @@ pub fn order_filled( strategy_id_ema_cross, instrument_id_btc_usdt, client_order_id, - VenueOrderId::new("123456").unwrap(), - AccountId::new("SIM-001").unwrap(), - TradeId::new("1").unwrap(), + VenueOrderId::new("123456"), + AccountId::new("SIM-001"), + TradeId::new("1"), OrderSide::Buy, OrderType::Limit, Quantity::from_str("0.561").unwrap(), @@ -64,7 +64,7 @@ pub fn order_filled( UnixNanos::default(), false, None, - Some(Money::from_str("12.2 USDT").unwrap()), + Some(Money::from("12.2 USDT")), ) .unwrap() } @@ -122,8 +122,8 @@ pub fn order_initialized_buy_limit( client_order_id: ClientOrderId, uuid4: UUID4, ) -> OrderInitialized { - let order_list_id = OrderListId::new("1").unwrap(); - let linked_order_ids = vec![ClientOrderId::new("O-2020872378424").unwrap()]; + let order_list_id = OrderListId::new("1"); + let linked_order_ids = vec![ClientOrderId::new("O-2020872378424")]; OrderInitialized::new( trader_id, strategy_id_ema_cross, diff --git a/nautilus_core/model/src/ffi/data/bar.rs b/nautilus_core/model/src/ffi/data/bar.rs index ce060300bf99..33a8c64e8303 100644 --- a/nautilus_core/model/src/ffi/data/bar.rs +++ b/nautilus_core/model/src/ffi/data/bar.rs @@ -200,11 +200,11 @@ pub extern "C" fn bar_new_from_raw( ) -> Bar { Bar { bar_type, - open: Price::from_raw(open, price_prec).unwrap(), - high: Price::from_raw(high, price_prec).unwrap(), - low: Price::from_raw(low, price_prec).unwrap(), - close: Price::from_raw(close, price_prec).unwrap(), - volume: Quantity::from_raw(volume, size_prec).unwrap(), + open: Price::from_raw(open, price_prec), + high: Price::from_raw(high, price_prec), + low: Price::from_raw(low, price_prec), + close: Price::from_raw(close, price_prec), + volume: Quantity::from_raw(volume, size_prec), ts_event, ts_init, } diff --git a/nautilus_core/model/src/ffi/data/order.rs b/nautilus_core/model/src/ffi/data/order.rs index 847e41957bf9..a740bf29a233 100644 --- a/nautilus_core/model/src/ffi/data/order.rs +++ b/nautilus_core/model/src/ffi/data/order.rs @@ -38,8 +38,8 @@ pub extern "C" fn book_order_from_raw( ) -> BookOrder { BookOrder::new( order_side, - Price::from_raw(price_raw, price_prec).unwrap(), - Quantity::from_raw(size_raw, size_prec).unwrap(), + Price::from_raw(price_raw, price_prec), + Quantity::from_raw(size_raw, size_prec), order_id, ) } diff --git a/nautilus_core/model/src/ffi/data/quote.rs b/nautilus_core/model/src/ffi/data/quote.rs index 9bb254117bdb..2973219d8fe3 100644 --- a/nautilus_core/model/src/ffi/data/quote.rs +++ b/nautilus_core/model/src/ffi/data/quote.rs @@ -43,10 +43,10 @@ pub extern "C" fn quote_tick_new( ) -> QuoteTick { QuoteTick::new( instrument_id, - Price::from_raw(bid_price_raw, bid_price_prec).unwrap(), - Price::from_raw(ask_price_raw, ask_price_prec).unwrap(), - Quantity::from_raw(bid_size_raw, bid_size_prec).unwrap(), - Quantity::from_raw(ask_size_raw, ask_size_prec).unwrap(), + Price::from_raw(bid_price_raw, bid_price_prec), + Price::from_raw(ask_price_raw, ask_price_prec), + Quantity::from_raw(bid_size_raw, bid_size_prec), + Quantity::from_raw(ask_size_raw, ask_size_prec), ts_event, ts_init, ) diff --git a/nautilus_core/model/src/ffi/data/trade.rs b/nautilus_core/model/src/ffi/data/trade.rs index 916d2ab83207..e53d4e79e958 100644 --- a/nautilus_core/model/src/ffi/data/trade.rs +++ b/nautilus_core/model/src/ffi/data/trade.rs @@ -42,8 +42,8 @@ pub extern "C" fn trade_tick_new( ) -> TradeTick { TradeTick::new( instrument_id, - Price::from_raw(price_raw, price_prec).unwrap(), - Quantity::from_raw(size_raw, size_prec).unwrap(), + Price::from_raw(price_raw, price_prec), + Quantity::from_raw(size_raw, size_prec), aggressor_side, trade_id, ts_event.into(), diff --git a/nautilus_core/model/src/ffi/identifiers/trade_id.rs b/nautilus_core/model/src/ffi/identifiers/trade_id.rs index f8da1e52a20c..f9f05e1d5d68 100644 --- a/nautilus_core/model/src/ffi/identifiers/trade_id.rs +++ b/nautilus_core/model/src/ffi/identifiers/trade_id.rs @@ -28,7 +28,7 @@ use crate::identifiers::trade_id::TradeId; /// - Assumes `ptr` is a valid C string pointer. #[no_mangle] pub unsafe extern "C" fn trade_id_new(ptr: *const c_char) -> TradeId { - TradeId::from_cstr(CStr::from_ptr(ptr).to_owned()).unwrap() + TradeId::from_cstr(CStr::from_ptr(ptr).to_owned()) } #[no_mangle] diff --git a/nautilus_core/model/src/ffi/types/currency.rs b/nautilus_core/model/src/ffi/types/currency.rs index 190dac9b8260..c8fcbd1396f3 100644 --- a/nautilus_core/model/src/ffi/types/currency.rs +++ b/nautilus_core/model/src/ffi/types/currency.rs @@ -40,7 +40,6 @@ pub unsafe extern "C" fn currency_from_py( cstr_to_str(name_ptr), currency_type, ) - .unwrap() } #[no_mangle] @@ -103,7 +102,7 @@ mod tests { #[rstest] fn test_registration() { - let currency = Currency::new("MYC", 4, 0, "My Currency", CurrencyType::Crypto).unwrap(); + let currency = Currency::new("MYC", 4, 0, "My Currency", CurrencyType::Crypto); currency_register(currency); unsafe { assert_eq!(currency_exists(str_to_cstr("MYC")), 1); diff --git a/nautilus_core/model/src/ffi/types/money.rs b/nautilus_core/model/src/ffi/types/money.rs index c78e30f5d0ab..1005676c708e 100644 --- a/nautilus_core/model/src/ffi/types/money.rs +++ b/nautilus_core/model/src/ffi/types/money.rs @@ -21,7 +21,7 @@ use crate::types::{currency::Currency, money::Money}; #[no_mangle] pub extern "C" fn money_new(amount: f64, currency: Currency) -> Money { // SAFETY: Assumes `amount` is properly validated - Money::new(amount, currency).unwrap() + Money::new(amount, currency) } #[no_mangle] diff --git a/nautilus_core/model/src/ffi/types/price.rs b/nautilus_core/model/src/ffi/types/price.rs index 182db374ced9..1ebc90871502 100644 --- a/nautilus_core/model/src/ffi/types/price.rs +++ b/nautilus_core/model/src/ffi/types/price.rs @@ -21,12 +21,12 @@ use crate::types::price::Price; #[no_mangle] pub extern "C" fn price_new(value: f64, precision: u8) -> Price { // SAFETY: Assumes `value` and `precision` are properly validated - Price::new(value, precision).unwrap() + Price::new(value, precision) } #[no_mangle] pub extern "C" fn price_from_raw(raw: i64, precision: u8) -> Price { - Price::from_raw(raw, precision).unwrap() + Price::from_raw(raw, precision) } #[no_mangle] diff --git a/nautilus_core/model/src/ffi/types/quantity.rs b/nautilus_core/model/src/ffi/types/quantity.rs index 68795d7a99dc..27f8a50afeb3 100644 --- a/nautilus_core/model/src/ffi/types/quantity.rs +++ b/nautilus_core/model/src/ffi/types/quantity.rs @@ -21,12 +21,12 @@ use crate::types::quantity::Quantity; #[no_mangle] pub extern "C" fn quantity_new(value: f64, precision: u8) -> Quantity { // SAFETY: Assumes `value` and `precision` are properly validated - Quantity::new(value, precision).unwrap() + Quantity::new(value, precision) } #[no_mangle] pub extern "C" fn quantity_from_raw(raw: u64, precision: u8) -> Quantity { - Quantity::from_raw(raw, precision).unwrap() + Quantity::from_raw(raw, precision) } #[no_mangle] diff --git a/nautilus_core/model/src/identifiers/account_id.rs b/nautilus_core/model/src/identifiers/account_id.rs index 3b9bc5c484b4..f1b9052cb2a9 100644 --- a/nautilus_core/model/src/identifiers/account_id.rs +++ b/nautilus_core/model/src/identifiers/account_id.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::{check_string_contains, check_valid_string}; +use nautilus_core::correctness::{check_string_contains, check_valid_string, FAILED}; use ustr::Ustr; use super::Venue; @@ -45,11 +45,10 @@ impl AccountId { /// # Panics /// /// Panics if `value` is not a valid string, or does not contain a hyphen '-' separator. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - check_string_contains(value, "-", stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + check_string_contains(value, "-", stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -96,12 +95,6 @@ impl Display for AccountId { } } -impl From<&str> for AccountId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// @@ -113,23 +106,21 @@ mod tests { use crate::identifiers::stubs::*; #[rstest] + #[should_panic] fn test_account_id_new_invalid_string() { - let s = ""; - let result = AccountId::new(s); - assert!(result.is_err()); + AccountId::new(""); } #[rstest] + #[should_panic] fn test_account_id_new_missing_hyphen() { - let s = "123456789"; - let result = AccountId::new(s); - assert!(result.is_err()); + AccountId::new("123456789"); } #[rstest] fn test_account_id_fmt() { let s = "IB-U123456789"; - let account_id = AccountId::new(s).unwrap(); + let account_id = AccountId::new(s); let formatted = format!("{account_id}"); assert_eq!(formatted, s); } @@ -141,7 +132,7 @@ mod tests { #[rstest] fn test_get_issuer(account_ib: AccountId) { - assert_eq!(account_ib.get_issuer(), Venue::new("IB").unwrap()); + assert_eq!(account_ib.get_issuer(), Venue::new("IB")); } #[rstest] diff --git a/nautilus_core/model/src/identifiers/client_id.rs b/nautilus_core/model/src/identifiers/client_id.rs index 0cb742ec52c5..389797d24435 100644 --- a/nautilus_core/model/src/identifiers/client_id.rs +++ b/nautilus_core/model/src/identifiers/client_id.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use ustr::Ustr; /// Represents a system client ID. @@ -38,10 +38,9 @@ impl ClientId { /// # Panics /// /// Panics if `value` is not a valid string. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -74,12 +73,6 @@ impl Display for ClientId { } } -impl From<&str> for ClientId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/client_order_id.rs b/nautilus_core/model/src/identifiers/client_order_id.rs index 0beed32f3cb0..77e8e552d22e 100644 --- a/nautilus_core/model/src/identifiers/client_order_id.rs +++ b/nautilus_core/model/src/identifiers/client_order_id.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use ustr::Ustr; /// Represents a valid client order ID (assigned by the Nautilus system). @@ -38,10 +38,9 @@ impl ClientOrderId { /// # Panics /// /// Panics if `value` is not a valid string. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -80,7 +79,7 @@ pub fn optional_ustr_to_vec_client_order_ids(s: Option) -> Option>() }) } @@ -97,12 +96,6 @@ pub fn optional_vec_client_order_ids_to_ustr(vec: Option>) -> }) } -impl From<&str> for ClientOrderId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/component_id.rs b/nautilus_core/model/src/identifiers/component_id.rs index 119c25d5cf56..4b8f4654c67b 100644 --- a/nautilus_core/model/src/identifiers/component_id.rs +++ b/nautilus_core/model/src/identifiers/component_id.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use ustr::Ustr; /// Represents a valid component ID. @@ -38,10 +38,9 @@ impl ComponentId { /// # Panics /// /// Panics if `value` is not a valid string. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -74,12 +73,6 @@ impl Display for ComponentId { } } -impl From<&str> for ComponentId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/exec_algorithm_id.rs b/nautilus_core/model/src/identifiers/exec_algorithm_id.rs index de79b2fbb69a..dacd96062114 100644 --- a/nautilus_core/model/src/identifiers/exec_algorithm_id.rs +++ b/nautilus_core/model/src/identifiers/exec_algorithm_id.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use ustr::Ustr; /// Represents a valid execution algorithm ID. @@ -38,10 +38,9 @@ impl ExecAlgorithmId { /// # Panics /// /// Panics if `value` is not a valid string. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -74,12 +73,6 @@ impl Display for ExecAlgorithmId { } } -impl From<&str> for ExecAlgorithmId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/instrument_id.rs b/nautilus_core/model/src/identifiers/instrument_id.rs index ce403bedec28..df17de738d3c 100644 --- a/nautilus_core/model/src/identifiers/instrument_id.rs +++ b/nautilus_core/model/src/identifiers/instrument_id.rs @@ -21,6 +21,7 @@ use std::{ str::FromStr, }; +use nautilus_core::correctness::check_valid_string; use serde::{Deserialize, Deserializer, Serialize}; use crate::identifiers::{Symbol, Venue}; @@ -59,12 +60,14 @@ impl FromStr for InstrumentId { fn from_str(s: &str) -> anyhow::Result { match s.rsplit_once('.') { - Some((symbol_part, venue_part)) => Ok(Self { - symbol: Symbol::new(symbol_part) - .map_err(|e| anyhow::anyhow!(err_message(s, e.to_string())))?, - venue: Venue::new(venue_part) - .map_err(|e| anyhow::anyhow!(err_message(s, e.to_string())))?, - }), + Some((symbol_part, venue_part)) => { + check_valid_string(symbol_part, stringify!(value))?; + check_valid_string(venue_part, stringify!(value))?; + Ok(Self { + symbol: Symbol::new(symbol_part), + venue: Venue::new(venue_part), + }) + } None => { anyhow::bail!(err_message( s, @@ -76,8 +79,22 @@ impl FromStr for InstrumentId { } impl From<&str> for InstrumentId { - fn from(input: &str) -> Self { - Self::from_str(input).unwrap() + fn from(s: &str) -> Self { + match s.rsplit_once('.') { + Some((symbol_part, venue_part)) => Self { + symbol: Symbol::new(symbol_part), + venue: Venue::new(venue_part), + }, + None => { + panic!( + "{}", + err_message( + s, + "Missing '.' separator between symbol and venue components".to_string() + ) + ) + } + } } } @@ -108,7 +125,7 @@ impl<'de> Deserialize<'de> for InstrumentId { D: Deserializer<'de>, { let instrument_id_str = String::deserialize(deserializer)?; - Self::from_str(&instrument_id_str).map_err(|err| serde::de::Error::custom(err.to_string())) + Ok(Self::from(instrument_id_str.as_str())) } } @@ -121,7 +138,6 @@ fn err_message(s: &str, e: String) -> String { //////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use std::str::FromStr; use rstest::rstest; @@ -135,15 +151,11 @@ mod tests { } #[rstest] + #[should_panic( + expected = "Error parsing `InstrumentId` from 'ETHUSDT-BINANCE': Missing '.' separator between symbol and venue components" + )] fn test_instrument_id_parse_failure_no_dot() { - let result = InstrumentId::from_str("ETHUSDT-BINANCE"); - assert!(result.is_err()); - - let error = result.unwrap_err(); - assert_eq!( - error.to_string(), - "Error parsing `InstrumentId` from 'ETHUSDT-BINANCE': Missing '.' separator between symbol and venue components" - ); + let _ = InstrumentId::from("ETHUSDT-BINANCE"); } #[rstest] diff --git a/nautilus_core/model/src/identifiers/macros.rs b/nautilus_core/model/src/identifiers/macros.rs index a9372b4f8f9d..9442463ec267 100644 --- a/nautilus_core/model/src/identifiers/macros.rs +++ b/nautilus_core/model/src/identifiers/macros.rs @@ -32,7 +32,7 @@ macro_rules! impl_serialization_for_identifier { D: Deserializer<'de>, { let value_str: &str = Deserialize::deserialize(deserializer)?; - let value: $ty = FromStr::from_str(value_str).map_err(serde::de::Error::custom)?; + let value: $ty = value_str.into(); Ok(value) } } @@ -41,11 +41,9 @@ macro_rules! impl_serialization_for_identifier { macro_rules! impl_from_str_for_identifier { ($ty:ty) => { - impl FromStr for $ty { - type Err = String; - - fn from_str(input: &str) -> Result { - Self::new(input).map_err(|e| e.to_string()) + impl From<&str> for $ty { + fn from(input: &str) -> Self { + Self::new(input) } } }; diff --git a/nautilus_core/model/src/identifiers/mod.rs b/nautilus_core/model/src/identifiers/mod.rs index c21c9e0f4076..cb8b702f64df 100644 --- a/nautilus_core/model/src/identifiers/mod.rs +++ b/nautilus_core/model/src/identifiers/mod.rs @@ -15,8 +15,6 @@ //! Identifiers for the trading domain model. -use std::str::FromStr; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[macro_use] diff --git a/nautilus_core/model/src/identifiers/order_list_id.rs b/nautilus_core/model/src/identifiers/order_list_id.rs index d59eb228c2eb..47b4c7fed7fb 100644 --- a/nautilus_core/model/src/identifiers/order_list_id.rs +++ b/nautilus_core/model/src/identifiers/order_list_id.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use ustr::Ustr; /// Represents a valid order list ID (assigned by the Nautilus system). @@ -38,10 +38,9 @@ impl OrderListId { /// # Panics /// /// Panics if `value` is not a valid string. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -74,12 +73,6 @@ impl Display for OrderListId { } } -impl From<&str> for OrderListId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/position_id.rs b/nautilus_core/model/src/identifiers/position_id.rs index 3beeca15e3f2..56b56ac32de6 100644 --- a/nautilus_core/model/src/identifiers/position_id.rs +++ b/nautilus_core/model/src/identifiers/position_id.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use ustr::Ustr; /// Represents a valid position ID. @@ -38,10 +38,9 @@ impl PositionId { /// # Panics /// /// Panics if `value` is not a valid string. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -73,12 +72,6 @@ impl Display for PositionId { } } -impl From<&str> for PositionId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/strategy_id.rs b/nautilus_core/model/src/identifiers/strategy_id.rs index c062cd26156c..0c0a129057fb 100644 --- a/nautilus_core/model/src/identifiers/strategy_id.rs +++ b/nautilus_core/model/src/identifiers/strategy_id.rs @@ -17,7 +17,7 @@ use std::fmt::{Debug, Display, Formatter}; -use nautilus_core::correctness::{check_string_contains, check_valid_string}; +use nautilus_core::correctness::{check_string_contains, check_valid_string, FAILED}; use ustr::Ustr; /// The identifier for all 'external' strategy IDs (not local to this system instance). @@ -47,13 +47,12 @@ impl StrategyId { /// # Panics /// /// Panics if `value` is not a valid string, or does not contain a hyphen '-' separator. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); if value != EXTERNAL_STRATEGY_ID { - check_string_contains(value, "-", stringify!(value))?; + check_string_contains(value, "-", stringify!(value)).expect(FAILED); } - - Ok(Self(Ustr::from(value))) + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -76,7 +75,7 @@ impl StrategyId { #[must_use] pub fn external() -> Self { // SAFETY:: Constant value is safe - Self::new(EXTERNAL_STRATEGY_ID).unwrap() + Self::new(EXTERNAL_STRATEGY_ID) } #[must_use] @@ -103,12 +102,6 @@ impl Display for StrategyId { } } -impl From<&str> for StrategyId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/symbol.rs b/nautilus_core/model/src/identifiers/symbol.rs index 25d4ccbee862..3755dc44358a 100644 --- a/nautilus_core/model/src/identifiers/symbol.rs +++ b/nautilus_core/model/src/identifiers/symbol.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use ustr::Ustr; /// Represents a valid ticker symbol ID for a tradable instrument. @@ -38,10 +38,9 @@ impl Symbol { /// # Panics /// /// Panics if `value` is not a valid string. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -85,12 +84,6 @@ impl From for Symbol { } } -impl From<&str> for Symbol { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/trade_id.rs b/nautilus_core/model/src/identifiers/trade_id.rs index 1f6ff7d48054..eba7e009832d 100644 --- a/nautilus_core/model/src/identifiers/trade_id.rs +++ b/nautilus_core/model/src/identifiers/trade_id.rs @@ -21,7 +21,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::{check_in_range_inclusive_usize, check_valid_string}; +use nautilus_core::correctness::{check_in_range_inclusive_usize, check_valid_string, FAILED}; use serde::{Deserialize, Deserializer, Serialize}; /// The maximum length of ASCII characters for a `TradeId` string value (including null terminator). @@ -52,20 +52,29 @@ impl TradeId { /// # Panics /// /// Panics if `value` is not a valid string, or value length is greater than 36. - pub fn new(value: &str) -> anyhow::Result { - let cstr = CString::new(value).expect("`CString` conversion failed"); - Self::from_cstr(cstr) + pub fn new(value: &str) -> Self { + // check that string is non-empty and within the expected length + check_in_range_inclusive_usize(value.len(), 1, TRADE_ID_LEN, stringify!(value)) + .expect(FAILED); + Self::from_valid_bytes(value.as_bytes()) } - pub fn from_cstr(cstr: CString) -> anyhow::Result { + pub fn from_cstr(cstr: CString) -> Self { + let cstr_str = cstr + .to_str() + .expect("TradeId expected valid string as `CString`"); + check_valid_string(cstr_str, stringify!(cstr)).expect(FAILED); let bytes = cstr.as_bytes_with_nul(); - check_valid_string(cstr.to_str()?, stringify!(cstr))?; - check_in_range_inclusive_usize(bytes.len(), 2, TRADE_ID_LEN, stringify!(cstr))?; + // check that string is non-empty excluding '\0' and within the expected length + check_in_range_inclusive_usize(bytes.len(), 2, TRADE_ID_LEN, stringify!(cstr)) + .expect(FAILED); + Self::from_valid_bytes(bytes) + } + fn from_valid_bytes(bytes: &[u8]) -> Self { let mut value = [0; TRADE_ID_LEN]; value[..bytes.len()].copy_from_slice(bytes); - - Ok(Self { value }) + Self { value } } #[must_use] @@ -82,12 +91,6 @@ impl Display for TradeId { } } -impl From<&str> for TradeId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - impl Serialize for TradeId { fn serialize(&self, serializer: S) -> Result where @@ -103,7 +106,7 @@ impl<'de> Deserialize<'de> for TradeId { D: Deserializer<'de>, { let value_str = String::deserialize(deserializer)?; - Self::new(&value_str).map_err(|err| serde::de::Error::custom(err.to_string())) + Ok(Self::new(&value_str)) } } diff --git a/nautilus_core/model/src/identifiers/trader_id.rs b/nautilus_core/model/src/identifiers/trader_id.rs index 5568fd22e2fe..6366a0dbb375 100644 --- a/nautilus_core/model/src/identifiers/trader_id.rs +++ b/nautilus_core/model/src/identifiers/trader_id.rs @@ -17,7 +17,7 @@ use std::fmt::{Debug, Display, Formatter}; -use nautilus_core::correctness::{check_string_contains, check_valid_string}; +use nautilus_core::correctness::{check_string_contains, check_valid_string, FAILED}; use ustr::Ustr; /// Represents a valid trader ID. @@ -44,11 +44,10 @@ impl TraderId { /// # Panics /// /// Panics if `value` is not a valid string, or does not contain a hyphen '-' separator. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - check_string_contains(value, "-", stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + check_string_contains(value, "-", stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -87,12 +86,6 @@ impl Display for TraderId { } } -impl From<&str> for TraderId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/venue.rs b/nautilus_core/model/src/identifiers/venue.rs index 48c40ba19031..cd6c2691d0cf 100644 --- a/nautilus_core/model/src/identifiers/venue.rs +++ b/nautilus_core/model/src/identifiers/venue.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use ustr::Ustr; use crate::venues::VENUE_MAP; @@ -42,10 +42,9 @@ impl Venue { /// # Panics /// /// Panics if `value` is not a valid string. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -83,7 +82,7 @@ impl Venue { #[must_use] pub fn synthetic() -> Self { // SAFETY: Unwrap safe as using known synthetic venue constant - Self::new(SYNTHETIC_VENUE).unwrap() + Self::new(SYNTHETIC_VENUE) } #[must_use] @@ -104,12 +103,6 @@ impl Display for Venue { } } -impl From<&str> for Venue { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/identifiers/venue_order_id.rs b/nautilus_core/model/src/identifiers/venue_order_id.rs index ad3189dea78c..1796ade6b44d 100644 --- a/nautilus_core/model/src/identifiers/venue_order_id.rs +++ b/nautilus_core/model/src/identifiers/venue_order_id.rs @@ -20,7 +20,7 @@ use std::{ hash::Hash, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use ustr::Ustr; /// Represents a valid venue order ID (assigned by a trading venue). @@ -38,10 +38,9 @@ impl VenueOrderId { /// # Panics /// /// Panics if `value` is not a valid string. - pub fn new(value: &str) -> anyhow::Result { - check_valid_string(value, stringify!(value))?; - - Ok(Self(Ustr::from(value))) + pub fn new(value: &str) -> Self { + check_valid_string(value, stringify!(value)).expect(FAILED); + Self(Ustr::from(value)) } /// Sets the inner identifier value. @@ -74,12 +73,6 @@ impl Display for VenueOrderId { } } -impl From<&str> for VenueOrderId { - fn from(input: &str) -> Self { - Self::new(input).unwrap() - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/model/src/instruments/any.rs b/nautilus_core/model/src/instruments/any.rs index 9e2632f1bb01..cb70f3869a5d 100644 --- a/nautilus_core/model/src/instruments/any.rs +++ b/nautilus_core/model/src/instruments/any.rs @@ -237,7 +237,7 @@ impl InstrumentAny { } } - pub fn make_price(&self, value: f64) -> anyhow::Result { + pub fn make_price(&self, value: f64) -> Price { match self { Self::CryptoFuture(inst) => inst.make_price(value), Self::CryptoPerpetual(inst) => inst.make_price(value), @@ -250,7 +250,7 @@ impl InstrumentAny { } } - pub fn make_qty(&self, value: f64) -> anyhow::Result { + pub fn make_qty(&self, value: f64) -> Quantity { match self { Self::CryptoFuture(inst) => inst.make_qty(value), Self::CryptoPerpetual(inst) => inst.make_qty(value), diff --git a/nautilus_core/model/src/instruments/crypto_future.rs b/nautilus_core/model/src/instruments/crypto_future.rs index bc8e40b84d95..d6a38b82207e 100644 --- a/nautilus_core/model/src/instruments/crypto_future.rs +++ b/nautilus_core/model/src/instruments/crypto_future.rs @@ -16,7 +16,7 @@ use std::hash::{Hash, Hasher}; use nautilus_core::{ - correctness::{check_equal_u8, check_positive_i64, check_positive_u64}, + correctness::{check_equal_u8, check_positive_i64, check_positive_u64, FAILED}, nanos::UnixNanos, }; use rust_decimal::Decimal; @@ -94,23 +94,25 @@ impl CryptoFuture { min_price: Option, ts_event: UnixNanos, ts_init: UnixNanos, - ) -> anyhow::Result { + ) -> Self { check_equal_u8( price_precision, price_increment.precision, stringify!(price_precision), stringify!(price_increment.precision), - )?; + ) + .expect(FAILED); check_equal_u8( size_precision, size_increment.precision, stringify!(size_precision), stringify!(size_increment.precision), - )?; - check_positive_i64(price_increment.raw, stringify!(price_increment.raw))?; - check_positive_u64(size_increment.raw, stringify!(size_increment.raw))?; + ) + .expect(FAILED); + check_positive_i64(price_increment.raw, stringify!(price_increment.raw)).expect(FAILED); + check_positive_u64(size_increment.raw, stringify!(size_increment.raw)).expect(FAILED); - Ok(Self { + Self { id, raw_symbol, underlying, @@ -136,7 +138,7 @@ impl CryptoFuture { min_price, ts_event, ts_init, - }) + } } } @@ -225,7 +227,7 @@ impl Instrument for CryptoFuture { fn multiplier(&self) -> Quantity { // SAFETY: Unwrap safe as using known values - Quantity::new(1.0, 0).unwrap() + Quantity::new(1.0, 0) } fn lot_size(&self) -> Option { diff --git a/nautilus_core/model/src/instruments/crypto_perpetual.rs b/nautilus_core/model/src/instruments/crypto_perpetual.rs index 612f9eb87cbb..9c764d1e7bda 100644 --- a/nautilus_core/model/src/instruments/crypto_perpetual.rs +++ b/nautilus_core/model/src/instruments/crypto_perpetual.rs @@ -16,7 +16,7 @@ use std::hash::{Hash, Hasher}; use nautilus_core::{ - correctness::{check_equal_u8, check_positive_i64, check_positive_u64}, + correctness::{check_equal_u8, check_positive_i64, check_positive_u64, FAILED}, nanos::UnixNanos, }; use rust_decimal::Decimal; @@ -91,23 +91,25 @@ impl CryptoPerpetual { min_price: Option, ts_event: UnixNanos, ts_init: UnixNanos, - ) -> anyhow::Result { + ) -> Self { check_equal_u8( price_precision, price_increment.precision, stringify!(price_precision), stringify!(price_increment.precision), - )?; + ) + .expect(FAILED); check_equal_u8( size_precision, size_increment.precision, stringify!(size_precision), stringify!(size_increment.precision), - )?; - check_positive_i64(price_increment.raw, stringify!(price_increment.raw))?; - check_positive_u64(size_increment.raw, stringify!(size_increment.raw))?; + ) + .expect(FAILED); + check_positive_i64(price_increment.raw, stringify!(price_increment.raw)).expect(FAILED); + check_positive_u64(size_increment.raw, stringify!(size_increment.raw)).expect(FAILED); - Ok(Self { + Self { id, raw_symbol, base_currency, @@ -131,7 +133,7 @@ impl CryptoPerpetual { min_price, ts_event, ts_init, - }) + } } } @@ -227,7 +229,7 @@ impl Instrument for CryptoPerpetual { } fn multiplier(&self) -> Quantity { - Quantity::new(1.0, 0).unwrap() + Quantity::new(1.0, 0) } fn lot_size(&self) -> Option { diff --git a/nautilus_core/model/src/instruments/currency_pair.rs b/nautilus_core/model/src/instruments/currency_pair.rs index 4e534123dafd..92d117c539e5 100644 --- a/nautilus_core/model/src/instruments/currency_pair.rs +++ b/nautilus_core/model/src/instruments/currency_pair.rs @@ -16,7 +16,7 @@ use std::hash::{Hash, Hasher}; use nautilus_core::{ - correctness::{check_equal_u8, check_positive_i64, check_positive_u64}, + correctness::{check_equal_u8, check_positive_i64, check_positive_u64, FAILED}, nanos::UnixNanos, }; use rust_decimal::Decimal; @@ -86,23 +86,25 @@ impl CurrencyPair { min_price: Option, ts_event: UnixNanos, ts_init: UnixNanos, - ) -> anyhow::Result { + ) -> Self { check_equal_u8( price_precision, price_increment.precision, stringify!(price_precision), stringify!(price_increment.precision), - )?; + ) + .expect(FAILED); check_equal_u8( size_precision, size_increment.precision, stringify!(size_precision), stringify!(size_increment.precision), - )?; - check_positive_i64(price_increment.raw, stringify!(price_increment.raw))?; - check_positive_u64(size_increment.raw, stringify!(size_increment.raw))?; + ) + .expect(FAILED); + check_positive_i64(price_increment.raw, stringify!(price_increment.raw)).expect(FAILED); + check_positive_u64(size_increment.raw, stringify!(size_increment.raw)).expect(FAILED); - Ok(Self { + Self { id, raw_symbol, base_currency, @@ -124,7 +126,7 @@ impl CurrencyPair { min_price, ts_event, ts_init, - }) + } } } @@ -203,7 +205,7 @@ impl Instrument for CurrencyPair { fn multiplier(&self) -> Quantity { // SAFETY: Unwrap safe as using known values - Quantity::new(1.0, 0).unwrap() + Quantity::new(1.0, 0) } fn lot_size(&self) -> Option { diff --git a/nautilus_core/model/src/instruments/equity.rs b/nautilus_core/model/src/instruments/equity.rs index 56f6bf3132da..58e2f40749ae 100644 --- a/nautilus_core/model/src/instruments/equity.rs +++ b/nautilus_core/model/src/instruments/equity.rs @@ -16,7 +16,7 @@ use std::hash::{Hash, Hasher}; use nautilus_core::{ - correctness::{check_equal_u8, check_positive_i64, check_valid_string_optional}, + correctness::{check_equal_u8, check_positive_i64, check_valid_string_optional, FAILED}, nanos::UnixNanos, }; use rust_decimal::Decimal; @@ -79,17 +79,18 @@ impl Equity { min_price: Option, ts_event: UnixNanos, ts_init: UnixNanos, - ) -> anyhow::Result { - check_valid_string_optional(isin.map(|u| u.as_str()), stringify!(isin))?; + ) -> Self { + check_valid_string_optional(isin.map(|u| u.as_str()), stringify!(isin)).expect(FAILED); check_equal_u8( price_precision, price_increment.precision, stringify!(price_precision), stringify!(price_increment.precision), - )?; - check_positive_i64(price_increment.raw, stringify!(price_increment.raw))?; + ) + .expect(FAILED); + check_positive_i64(price_increment.raw, stringify!(price_increment.raw)).expect(FAILED); - Ok(Self { + Self { id, raw_symbol, isin, @@ -107,7 +108,7 @@ impl Equity { min_price, ts_event, ts_init, - }) + } } } diff --git a/nautilus_core/model/src/instruments/futures_contract.rs b/nautilus_core/model/src/instruments/futures_contract.rs index efb5a0ef01be..7168ca755763 100644 --- a/nautilus_core/model/src/instruments/futures_contract.rs +++ b/nautilus_core/model/src/instruments/futures_contract.rs @@ -17,7 +17,7 @@ use std::hash::{Hash, Hasher}; use nautilus_core::{ correctness::{ - check_equal_u8, check_positive_i64, check_valid_string, check_valid_string_optional, + check_equal_u8, check_positive_i64, check_valid_string, check_valid_string_optional, FAILED, }, nanos::UnixNanos, }; @@ -89,18 +89,18 @@ impl FuturesContract { margin_maint: Option, ts_event: UnixNanos, ts_init: UnixNanos, - ) -> anyhow::Result { - check_valid_string_optional(exchange.map(|u| u.as_str()), stringify!(isin))?; - check_valid_string(underlying.as_str(), stringify!(underlying))?; + ) -> Self { + check_valid_string_optional(exchange.map(|u| u.as_str()), stringify!(isin)).expect(FAILED); + check_valid_string(underlying.as_str(), stringify!(underlying)).expect(FAILED); check_equal_u8( price_precision, price_increment.precision, stringify!(price_precision), stringify!(price_increment.precision), - )?; - check_positive_i64(price_increment.raw, stringify!(price_increment.raw))?; - - Ok(Self { + ) + .expect(FAILED); + check_positive_i64(price_increment.raw, stringify!(price_increment.raw)).expect(FAILED); + Self { id, raw_symbol, asset_class, @@ -112,7 +112,7 @@ impl FuturesContract { price_precision, price_increment, size_precision: 0, - size_increment: Quantity::from("1"), + size_increment: Quantity::from(1), multiplier, lot_size, margin_init: margin_init.unwrap_or(0.into()), @@ -123,7 +123,7 @@ impl FuturesContract { min_price, ts_event, ts_init, - }) + } } } diff --git a/nautilus_core/model/src/instruments/futures_spread.rs b/nautilus_core/model/src/instruments/futures_spread.rs index 7835445e385c..d85a7e732c4f 100644 --- a/nautilus_core/model/src/instruments/futures_spread.rs +++ b/nautilus_core/model/src/instruments/futures_spread.rs @@ -17,7 +17,7 @@ use std::hash::{Hash, Hasher}; use nautilus_core::{ correctness::{ - check_equal_u8, check_positive_i64, check_valid_string, check_valid_string_optional, + check_equal_u8, check_positive_i64, check_valid_string, check_valid_string_optional, FAILED, }, nanos::UnixNanos, }; @@ -91,19 +91,19 @@ impl FuturesSpread { margin_maint: Option, ts_event: UnixNanos, ts_init: UnixNanos, - ) -> anyhow::Result { - check_valid_string_optional(exchange.map(|u| u.as_str()), stringify!(isin))?; - check_valid_string(underlying.as_str(), stringify!(underlying))?; - check_valid_string(strategy_type.as_str(), stringify!(strategy_type))?; + ) -> Self { + check_valid_string_optional(exchange.map(|u| u.as_str()), stringify!(isin)).expect(FAILED); + check_valid_string(underlying.as_str(), stringify!(underlying)).expect(FAILED); + check_valid_string(strategy_type.as_str(), stringify!(strategy_type)).expect(FAILED); check_equal_u8( price_precision, price_increment.precision, stringify!(price_precision), stringify!(price_increment.precision), - )?; - check_positive_i64(price_increment.raw, stringify!(price_increment.raw))?; - - Ok(Self { + ) + .expect(FAILED); + check_positive_i64(price_increment.raw, stringify!(price_increment.raw)).expect(FAILED); + Self { id, raw_symbol, asset_class, @@ -127,7 +127,7 @@ impl FuturesSpread { min_price, ts_event, ts_init, - }) + } } } diff --git a/nautilus_core/model/src/instruments/mod.rs b/nautilus_core/model/src/instruments/mod.rs index 0b33b9363d6f..75512bfd0db6 100644 --- a/nautilus_core/model/src/instruments/mod.rs +++ b/nautilus_core/model/src/instruments/mod.rs @@ -95,12 +95,12 @@ pub trait Instrument: 'static + Send { fn ts_init(&self) -> UnixNanos; /// Creates a new `Price` from the given `value` with the correct price precision for the instrument. - fn make_price(&self, value: f64) -> anyhow::Result { + fn make_price(&self, value: f64) -> Price { Price::new(value, self.price_precision()) } /// Creates a new `Quantity` from the given `value` with the correct size precision for the instrument. - fn make_qty(&self, value: f64) -> anyhow::Result { + fn make_qty(&self, value: f64) -> Quantity { Quantity::new(value, self.size_precision()) } @@ -134,13 +134,13 @@ pub trait Instrument: 'static + Send { (amount, currency) }; - Money::new(amount, currency).unwrap() // TODO: Handle error properly + Money::new(amount, currency) } /// Returns the equivalent quantity of the base asset. fn calculate_base_quantity(&self, quantity: Quantity, last_px: Price) -> Quantity { let value = quantity.as_f64() * (1.0 / last_px.as_f64()); - Quantity::new(value, self.size_precision()).unwrap() // TODO: Handle error properly + Quantity::new(value, self.size_precision()) } } diff --git a/nautilus_core/model/src/instruments/options_contract.rs b/nautilus_core/model/src/instruments/options_contract.rs index 9395e04ba410..85c0c586cf6c 100644 --- a/nautilus_core/model/src/instruments/options_contract.rs +++ b/nautilus_core/model/src/instruments/options_contract.rs @@ -17,7 +17,7 @@ use std::hash::{Hash, Hasher}; use nautilus_core::{ correctness::{ - check_equal_u8, check_positive_i64, check_valid_string, check_valid_string_optional, + check_equal_u8, check_positive_i64, check_valid_string, check_valid_string_optional, FAILED, }, nanos::UnixNanos, }; @@ -93,18 +93,19 @@ impl OptionsContract { margin_maint: Option, ts_event: UnixNanos, ts_init: UnixNanos, - ) -> anyhow::Result { - check_valid_string_optional(exchange.map(|u| u.as_str()), stringify!(isin))?; - check_valid_string(underlying.as_str(), stringify!(underlying))?; + ) -> Self { + check_valid_string_optional(exchange.map(|u| u.as_str()), stringify!(isin)).expect(FAILED); + check_valid_string(underlying.as_str(), stringify!(underlying)).expect(FAILED); check_equal_u8( price_precision, price_increment.precision, stringify!(price_precision), stringify!(price_increment.precision), - )?; - check_positive_i64(price_increment.raw, stringify!(price_increment.raw))?; + ) + .expect(FAILED); + check_positive_i64(price_increment.raw, stringify!(price_increment.raw)).expect(FAILED); - Ok(Self { + Self { id, raw_symbol, asset_class, @@ -129,7 +130,7 @@ impl OptionsContract { margin_maint: margin_maint.unwrap_or(0.into()), ts_event, ts_init, - }) + } } } diff --git a/nautilus_core/model/src/instruments/options_spread.rs b/nautilus_core/model/src/instruments/options_spread.rs index f68e32def8c3..b739a525f24a 100644 --- a/nautilus_core/model/src/instruments/options_spread.rs +++ b/nautilus_core/model/src/instruments/options_spread.rs @@ -17,7 +17,7 @@ use std::hash::{Hash, Hasher}; use nautilus_core::{ correctness::{ - check_equal_u8, check_positive_i64, check_valid_string, check_valid_string_optional, + check_equal_u8, check_positive_i64, check_valid_string, check_valid_string_optional, FAILED, }, nanos::UnixNanos, }; @@ -91,19 +91,19 @@ impl OptionsSpread { min_price: Option, ts_event: UnixNanos, ts_init: UnixNanos, - ) -> anyhow::Result { - check_valid_string_optional(exchange.map(|u| u.as_str()), stringify!(isin))?; - check_valid_string(underlying.as_str(), stringify!(underlying))?; - check_valid_string(strategy_type.as_str(), stringify!(strategy_type))?; + ) -> Self { + check_valid_string_optional(exchange.map(|u| u.as_str()), stringify!(isin)).expect(FAILED); + check_valid_string(underlying.as_str(), stringify!(underlying)).expect(FAILED); + check_valid_string(strategy_type.as_str(), stringify!(strategy_type)).expect(FAILED); check_equal_u8( price_precision, price_increment.precision, stringify!(price_precision), stringify!(price_increment.precision), - )?; - check_positive_i64(price_increment.raw, stringify!(price_increment.raw))?; - - Ok(Self { + ) + .expect(FAILED); + check_positive_i64(price_increment.raw, stringify!(price_increment.raw)).expect(FAILED); + Self { id, raw_symbol, asset_class, @@ -127,7 +127,7 @@ impl OptionsSpread { min_price, ts_event, ts_init, - }) + } } } diff --git a/nautilus_core/model/src/instruments/stubs.rs b/nautilus_core/model/src/instruments/stubs.rs index 241f5417dae9..07c8a866f448 100644 --- a/nautilus_core/model/src/instruments/stubs.rs +++ b/nautilus_core/model/src/instruments/stubs.rs @@ -40,7 +40,7 @@ impl Default for SyntheticInstrument { let ltc_binance = InstrumentId::from("LTC.BINANCE"); let formula = "(BTC.BINANCE + LTC.BINANCE) / 2.0".to_string(); SyntheticInstrument::new( - Symbol::new("BTC-LTC").unwrap(), + Symbol::new("BTC-LTC"), 2, vec![btc_binance, ltc_binance], formula.clone(), @@ -85,13 +85,12 @@ pub fn crypto_future_btcusdt( Some(Quantity::from("9000.0")), Some(Quantity::from("0.000001")), None, - Some(Money::new(10.00, Currency::from("USDT")).unwrap()), + Some(Money::new(10.00, Currency::from("USDT"))), Some(Price::from("1000000.00")), Some(Price::from("0.01")), 0.into(), 0.into(), ) - .unwrap() } //////////////////////////////////////////////////////////////////////////////// @@ -119,13 +118,12 @@ pub fn crypto_perpetual_ethusdt() -> CryptoPerpetual { Some(Quantity::from("10000.0")), Some(Quantity::from("0.001")), None, - Some(Money::new(10.00, Currency::from("USDT")).unwrap()), + Some(Money::new(10.00, Currency::from("USDT"))), Some(Price::from("15000.00")), Some(Price::from("1.0")), 0.into(), 0.into(), ) - .unwrap() } #[fixture] @@ -155,7 +153,6 @@ pub fn xbtusd_bitmex() -> CryptoPerpetual { 0.into(), 0.into(), ) - .unwrap() } #[fixture] @@ -185,7 +182,6 @@ pub fn ethusdt_bitmex() -> CryptoPerpetual { 0.into(), 0.into(), ) - .unwrap() } //////////////////////////////////////////////////////////////////////////////// @@ -217,7 +213,6 @@ pub fn currency_pair_btcusdt() -> CurrencyPair { 0.into(), 0.into(), ) - .unwrap() } #[fixture] @@ -245,7 +240,6 @@ pub fn currency_pair_ethusdt() -> CurrencyPair { 0.into(), 0.into(), ) - .unwrap() } #[must_use] @@ -255,7 +249,7 @@ pub fn default_fx_ccy(symbol: Symbol, venue: Option) -> CurrencyPair { let base_currency = symbol.as_str().split('/').next().unwrap(); let quote_currency = symbol.as_str().split('/').last().unwrap(); let price_precision = if quote_currency == "JPY" { 3 } else { 5 }; - let price_increment = Price::new(1.0 / 10.0f64, price_precision).unwrap(); + let price_increment = Price::new(1.0 / 10.0f64, price_precision); CurrencyPair::new( instrument_id, symbol, @@ -279,7 +273,6 @@ pub fn default_fx_ccy(symbol: Symbol, venue: Option) -> CurrencyPair { 0.into(), 0.into(), ) - .unwrap() } #[fixture] pub fn audusd_sim() -> CurrencyPair { @@ -316,7 +309,6 @@ pub fn equity_aapl() -> Equity { 0.into(), 0.into(), ) - .unwrap() } //////////////////////////////////////////////////////////////////////////////// @@ -361,7 +353,6 @@ pub fn futures_contract_es( 0.into(), 0.into(), ) - .unwrap() } //////////////////////////////////////////////////////////////////////////////// @@ -395,7 +386,6 @@ pub fn futures_spread_es() -> FuturesSpread { 0.into(), 0.into(), ) - .unwrap() } //////////////////////////////////////////////////////////////////////////////// @@ -430,7 +420,6 @@ pub fn options_contract_appl() -> OptionsContract { 0.into(), 0.into(), ) - .unwrap() } //////////////////////////////////////////////////////////////////////////////// @@ -464,5 +453,4 @@ pub fn options_spread() -> OptionsSpread { 0.into(), 0.into(), ) - .unwrap() } diff --git a/nautilus_core/model/src/instruments/synthetic.rs b/nautilus_core/model/src/instruments/synthetic.rs index 9aeca41f4994..128e50ab661a 100644 --- a/nautilus_core/model/src/instruments/synthetic.rs +++ b/nautilus_core/model/src/instruments/synthetic.rs @@ -57,7 +57,7 @@ impl SyntheticInstrument { ts_event: UnixNanos, ts_init: UnixNanos, ) -> anyhow::Result { - let price_increment = Price::new(10f64.powi(-i32::from(price_precision)), price_precision)?; + let price_increment = Price::new(10f64.powi(-i32::from(price_precision)), price_precision); // Extract variables from the component instruments let variables: Vec = components @@ -103,7 +103,8 @@ impl SyntheticInstrument { if let Some(&value) = inputs.get(variable) { input_values.push(value); self.context - .set_value(variable.clone(), Value::from(value))?; + .set_value(variable.clone(), Value::from(value)) + .expect("TODO: Unable to set value"); } else { panic!("Missing price for component: {variable}"); } @@ -127,7 +128,7 @@ impl SyntheticInstrument { let result: Value = self.operator_tree.eval_with_context(&self.context)?; match result { - Value::Float(price) => Price::new(price, self.price_precision), + Value::Float(price) => Ok(Price::new(price, self.price_precision)), _ => Err(anyhow::anyhow!( "Failed to evaluate formula to a floating point number" )), diff --git a/nautilus_core/model/src/orderbook/book.rs b/nautilus_core/model/src/orderbook/book.rs index 4fa61c556da1..e3fd6aae41ff 100644 --- a/nautilus_core/model/src/orderbook/book.rs +++ b/nautilus_core/model/src/orderbook/book.rs @@ -595,7 +595,7 @@ mod tests { price, size, AggressorSide::Buyer, - TradeId::new("123456789").unwrap(), + TradeId::new("123456789"), 0.into(), 0.into(), ); diff --git a/nautilus_core/model/src/orders/list.rs b/nautilus_core/model/src/orders/list.rs index b4ca79c47330..9205dbf2f4fa 100644 --- a/nautilus_core/model/src/orders/list.rs +++ b/nautilus_core/model/src/orders/list.rs @@ -42,20 +42,19 @@ impl OrderList { strategy_id: StrategyId, orders: Vec, ts_init: UnixNanos, - ) -> anyhow::Result { - check_slice_not_empty(orders.as_slice(), stringify!(orders))?; + ) -> Self { + check_slice_not_empty(orders.as_slice(), stringify!(orders)).unwrap(); for order in &orders { assert_eq!(instrument_id, order.instrument_id()); assert_eq!(strategy_id, order.strategy_id()); } - - Ok(Self { + Self { id: order_list_id, instrument_id, strategy_id, orders, ts_init, - }) + } } } @@ -132,8 +131,7 @@ mod tests { StrategyId::default(), orders, UnixNanos::default(), - ) - .unwrap(); + ); assert!(order_list.to_string().starts_with( "OrderList(id=OL-001, instrument_id=AUD/USD.SIM, strategy_id=S-001, orders=" diff --git a/nautilus_core/model/src/orders/stubs.rs b/nautilus_core/model/src/orders/stubs.rs index 0b9fd5ce292a..f2c486a9a2d9 100644 --- a/nautilus_core/model/src/orders/stubs.rs +++ b/nautilus_core/model/src/orders/stubs.rs @@ -87,15 +87,15 @@ impl TestOrderEventStubs { let account_id = account_id .or(order.account_id()) .unwrap_or(AccountId::from("SIM-001")); - let trade_id = trade_id.unwrap_or( - TradeId::new(order.client_order_id().as_str().replace('O', "E").as_str()).unwrap(), - ); + let trade_id = trade_id.unwrap_or(TradeId::new( + order.client_order_id().as_str().replace('O', "E").as_str(), + )); let liquidity_side = liquidity_side.unwrap_or(LiquiditySide::Maker); let event = UUID4::new(); let position_id = position_id .or_else(|| order.position_id()) - .unwrap_or(PositionId::new("1").unwrap()); - let commission = commission.unwrap_or(Money::from_str("2 USD").unwrap()); + .unwrap_or(PositionId::new("1")); + let commission = commission.unwrap_or(Money::from("2 USD")); let last_px = last_px.unwrap_or(Price::from_str("1.0").unwrap()); let last_qty = last_qty.unwrap_or(order.quantity()); let event = OrderFilled::new( diff --git a/nautilus_core/model/src/position.rs b/nautilus_core/model/src/position.rs index 1b77b9bf2110..f06607775ef2 100644 --- a/nautilus_core/model/src/position.rs +++ b/nautilus_core/model/src/position.rs @@ -83,7 +83,7 @@ pub struct Position { impl Position { /// Creates a new [`Position`] instance. - pub fn new(instrument: &InstrumentAny, fill: OrderFilled) -> anyhow::Result { + pub fn new(instrument: &InstrumentAny, fill: OrderFilled) -> Self { assert_eq!(instrument.id(), fill.instrument_id); assert_ne!(fill.order_side, OrderSide::NoOrderSide); @@ -125,13 +125,13 @@ impl Position { realized_pnl: None, }; item.apply(&fill); - Ok(item) + item } pub fn apply(&mut self, fill: &OrderFilled) { assert!( !self.trade_ids.contains(&fill.trade_id), - "`fill.trade_id` already contained in `trade_ids", + "`fill.trade_id` already contained in `trade_ids" ); if self.side == PositionSide::Flat { @@ -179,7 +179,7 @@ impl Position { // Set quantities // SAFETY: size_precision is valid from instrument - self.quantity = Quantity::new(self.signed_qty.abs(), self.size_precision).unwrap(); + self.quantity = Quantity::new(self.signed_qty.abs(), self.size_precision); if self.quantity > self.peak_qty { self.peak_qty.raw = self.quantity.raw; } @@ -232,15 +232,12 @@ impl Position { } if self.realized_pnl.is_none() { - self.realized_pnl = Some(Money::new(realized_pnl, self.settlement_currency).unwrap()); + self.realized_pnl = Some(Money::new(realized_pnl, self.settlement_currency)); } else { - self.realized_pnl = Some( - Money::new( - self.realized_pnl.unwrap().as_f64() + realized_pnl, - self.settlement_currency, - ) - .unwrap(), - ); + self.realized_pnl = Some(Money::new( + self.realized_pnl.unwrap().as_f64() + realized_pnl, + self.settlement_currency, + )); } self.signed_qty += last_qty; @@ -273,15 +270,12 @@ impl Position { } if self.realized_pnl.is_none() { - self.realized_pnl = Some(Money::new(realized_pnl, self.settlement_currency).unwrap()); + self.realized_pnl = Some(Money::new(realized_pnl, self.settlement_currency)); } else { - self.realized_pnl = Some( - Money::new( - self.realized_pnl.unwrap().as_f64() + realized_pnl, - self.settlement_currency, - ) - .unwrap(), - ); + self.realized_pnl = Some(Money::new( + self.realized_pnl.unwrap().as_f64() + realized_pnl, + self.settlement_currency, + )); } self.signed_qty -= last_qty; @@ -325,7 +319,6 @@ impl Position { realized_pnl + self.unrealized_pnl(last).as_f64(), self.settlement_currency, ) - .unwrap() } fn calculate_points(&self, avg_px_open: f64, avg_px_close: f64) -> f64 { @@ -349,19 +342,19 @@ impl Position { #[must_use] pub fn calculate_pnl(&self, avg_px_open: f64, avg_px_close: f64, quantity: Quantity) -> Money { let pnl_raw = self.calculate_pnl_raw(avg_px_open, avg_px_close, quantity.as_f64()); - Money::new(pnl_raw, self.settlement_currency).unwrap() + Money::new(pnl_raw, self.settlement_currency) } #[must_use] pub fn unrealized_pnl(&self, last: Price) -> Money { if self.side == PositionSide::Flat { - Money::new(0.0, self.settlement_currency).unwrap() + Money::new(0.0, self.settlement_currency) } else { let avg_px_open = self.avg_px_open; let avg_px_close = last.as_f64(); let quantity = self.quantity.as_f64(); let pnl = self.calculate_pnl_raw(avg_px_open, avg_px_close, quantity); - Money::new(pnl, self.settlement_currency).unwrap() + Money::new(pnl, self.settlement_currency) } } @@ -449,20 +442,20 @@ impl Position { self.quantity.as_f64() * self.multiplier.as_f64() * (1.0 / last.as_f64()), self.base_currency.unwrap(), ) - .unwrap() } else { Money::new( self.quantity.as_f64() * last.as_f64() * self.multiplier.as_f64(), self.quote_currency, ) - .unwrap() } } #[must_use] pub fn last_event(&self) -> OrderFilled { - // SAFETY: Position invariant guarantees at least one event - *self.events.last().unwrap() + *self + .events + .last() + .expect("Position invariant guarantees at least one event") } #[must_use] @@ -582,7 +575,7 @@ mod tests { let fill1 = TestOrderEventStubs::order_filled( &order1, &audusd_sim, - Some(TradeId::new("1").unwrap()), + Some(TradeId::new("1")), None, Some(Price::from("1.00001")), None, @@ -594,7 +587,7 @@ mod tests { let fill2 = TestOrderEventStubs::order_filled( &order2, &audusd_sim, - Some(TradeId::new("1").unwrap()), + Some(TradeId::new("1")), None, Some(Price::from("1.00002")), None, @@ -603,7 +596,7 @@ mod tests { None, None, ); - let mut position = Position::new(&audusd_sim, fill1.into()).unwrap(); + let mut position = Position::new(&audusd_sim, fill1.into()); position.apply(&fill2.into()); } @@ -630,7 +623,7 @@ mod tests { None, ); let last_price = Price::from_str("1.0005").unwrap(); - let position = Position::new(&audusd_sim, fill.into()).unwrap(); + let position = Position::new(&audusd_sim, fill.into()); assert_eq!(position.symbol(), audusd_sim.id().symbol); assert_eq!(position.venue(), audusd_sim.id().venue); assert!(!position.is_opposite_side(OrderSide::Buy)); @@ -646,29 +639,17 @@ mod tests { assert_eq!(position.duration_ns, 0); assert_eq!(position.avg_px_open, 1.00001); assert_eq!(position.event_count(), 1); - assert_eq!(position.id, PositionId::new("1").unwrap()); + assert_eq!(position.id, PositionId::new("1")); assert_eq!(position.events.len(), 1); assert!(position.is_long()); assert!(!position.is_short()); assert!(position.is_open()); assert!(!position.is_closed()); assert_eq!(position.realized_return, 0.0); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("-2.0 USD").unwrap()) - ); - assert_eq!( - position.unrealized_pnl(last_price), - Money::from_str("49.0 USD").unwrap() - ); - assert_eq!( - position.total_pnl(last_price), - Money::from_str("47.0 USD").unwrap() - ); - assert_eq!( - position.commissions(), - vec![Money::from_str("2.0 USD").unwrap()] - ); + assert_eq!(position.realized_pnl, Some(Money::from("-2.0 USD"))); + assert_eq!(position.unrealized_pnl(last_price), Money::from("49.0 USD")); + assert_eq!(position.total_pnl(last_price), Money::from("47.0 USD")); + assert_eq!(position.commissions(), vec![Money::from("2.0 USD")]); assert_eq!( format!("{position}"), "Position(LONG 100_000 AUD/USD.SIM, id=1)" @@ -698,7 +679,7 @@ mod tests { None, ); let last_price = Price::from_str("1.00050").unwrap(); - let position = Position::new(&audusd_sim, fill.into()).unwrap(); + let position = Position::new(&audusd_sim, fill.into()); assert_eq!(position.symbol(), audusd_sim.id().symbol); assert_eq!(position.venue(), audusd_sim.id().venue); assert!(!position.is_opposite_side(OrderSide::Sell)); @@ -712,29 +693,20 @@ mod tests { assert_eq!(position.ts_opened.as_u64(), 0); assert_eq!(position.avg_px_open, 1.00001); assert_eq!(position.event_count(), 1); - assert_eq!(position.id, PositionId::new("1").unwrap()); + assert_eq!(position.id, PositionId::new("1")); assert_eq!(position.events.len(), 1); assert!(!position.is_long()); assert!(position.is_short()); assert!(position.is_open()); assert!(!position.is_closed()); assert_eq!(position.realized_return, 0.0); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("-2.0 USD").unwrap()) - ); + assert_eq!(position.realized_pnl, Some(Money::from("-2.0 USD"))); assert_eq!( position.unrealized_pnl(last_price), - Money::from_str("-49.0 USD").unwrap() - ); - assert_eq!( - position.total_pnl(last_price), - Money::from_str("-51.0 USD").unwrap() - ); - assert_eq!( - position.commissions(), - vec![Money::from_str("2.0 USD").unwrap()] + Money::from("-49.0 USD") ); + assert_eq!(position.total_pnl(last_price), Money::from("-51.0 USD")); + assert_eq!(position.commissions(), vec![Money::from("2.0 USD")]); assert_eq!( format!("{position}"), "Position(SHORT 100_000 AUD/USD.SIM, id=1)" @@ -764,7 +736,7 @@ mod tests { None, ); let last_price = Price::from_str("1.00048").unwrap(); - let position = Position::new(&audusd_sim, fill.into()).unwrap(); + let position = Position::new(&audusd_sim, fill.into()); assert_eq!(position.quantity, Quantity::from(50_000)); assert_eq!(position.peak_qty, Quantity::from(50_000)); assert_eq!(position.side, PositionSide::Long); @@ -777,22 +749,10 @@ mod tests { assert!(position.is_open()); assert!(!position.is_closed()); assert_eq!(position.realized_return, 0.0); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("-2.0 USD").unwrap()) - ); - assert_eq!( - position.unrealized_pnl(last_price), - Money::from_str("23.5 USD").unwrap() - ); - assert_eq!( - position.total_pnl(last_price), - Money::from_str("21.5 USD").unwrap() - ); - assert_eq!( - position.commissions(), - vec![Money::from_str("2.0 USD").unwrap()] - ); + assert_eq!(position.realized_pnl, Some(Money::from("-2.0 USD"))); + assert_eq!(position.unrealized_pnl(last_price), Money::from("23.5 USD")); + assert_eq!(position.total_pnl(last_price), Money::from("21.5 USD")); + assert_eq!(position.commissions(), vec![Money::from("2.0 USD")]); assert_eq!( format!("{position}"), "Position(LONG 50_000 AUD/USD.SIM, id=1)" @@ -812,7 +772,7 @@ mod tests { let fill1 = TestOrderEventStubs::order_filled( &order, &audusd_sim, - Some(TradeId::new("1").unwrap()), + Some(TradeId::new("1")), None, Some(Price::from("1.00001")), Some(Quantity::from(50_000)), @@ -824,7 +784,7 @@ mod tests { let fill2 = TestOrderEventStubs::order_filled( &order, &audusd_sim, - Some(TradeId::new("2").unwrap()), + Some(TradeId::new("2")), None, Some(Price::from("1.00002")), Some(Quantity::from(50_000)), @@ -834,7 +794,7 @@ mod tests { None, ); let last_price = Price::from_str("1.0005").unwrap(); - let mut position = Position::new(&audusd_sim, fill1.into()).unwrap(); + let mut position = Position::new(&audusd_sim, fill1.into()); position.apply(&fill2.into()); assert_eq!(position.quantity, Quantity::from(100_000)); @@ -849,22 +809,13 @@ mod tests { assert!(position.is_open()); assert!(!position.is_closed()); assert_eq!(position.realized_return, 0.0); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("-4.0 USD").unwrap()) - ); + assert_eq!(position.realized_pnl, Some(Money::from("-4.0 USD"))); assert_eq!( position.unrealized_pnl(last_price), - Money::from_str("-48.5 USD").unwrap() - ); - assert_eq!( - position.total_pnl(last_price), - Money::from_str("-52.5 USD").unwrap() - ); - assert_eq!( - position.commissions(), - vec![Money::from_str("4.0 USD").unwrap()] + Money::from("-48.5 USD") ); + assert_eq!(position.total_pnl(last_price), Money::from("-52.5 USD")); + assert_eq!(position.commissions(), vec![Money::from("4.0 USD")]); } #[rstest] @@ -880,8 +831,8 @@ mod tests { let fill = TestOrderEventStubs::order_filled( &order, &audusd_sim, - Some(TradeId::new("1").unwrap()), - Some(PositionId::new("P-1").unwrap()), + Some(TradeId::new("1")), + Some(PositionId::new("P-1")), Some(Price::from("1.00001")), None, None, @@ -889,18 +840,16 @@ mod tests { Some(UnixNanos::from(1_000_000_000)), None, ); - let mut position = Position::new(&audusd_sim, fill.into()).unwrap(); + let mut position = Position::new(&audusd_sim, fill.into()); let fill2 = OrderFilled::new( order.trader_id(), - StrategyId::new("S-001").unwrap(), + StrategyId::new("S-001"), order.instrument_id(), order.client_order_id(), VenueOrderId::from("2"), - order - .account_id() - .unwrap_or(AccountId::new("SIM-001").unwrap()), - TradeId::new("2").unwrap(), + order.account_id().unwrap_or(AccountId::new("SIM-001")), + TradeId::new("2"), OrderSide::Sell, OrderType::Market, order.quantity(), @@ -911,8 +860,8 @@ mod tests { 2_000_000_000.into(), 0.into(), false, - Some(PositionId::new("T1").unwrap()), - Some(Money::from_str("0.0 USD").unwrap()), + Some(PositionId::new("T1")), + Some(Money::from("0.0 USD")), ) .unwrap(); position.apply(&fill2); @@ -936,19 +885,10 @@ mod tests { assert!(!position.is_open()); assert!(position.is_closed()); assert_eq!(position.realized_return, 9.999_900_000_998_888e-5); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("13.0 USD").unwrap()) - ); - assert_eq!( - position.unrealized_pnl(last), - Money::from_str("0 USD").unwrap() - ); - assert_eq!( - position.commissions(), - vec![Money::from_str("2 USD").unwrap()] - ); - assert_eq!(position.total_pnl(last), Money::from_str("13 USD").unwrap()); + assert_eq!(position.realized_pnl, Some(Money::from("13.0 USD"))); + assert_eq!(position.unrealized_pnl(last), Money::from("0 USD")); + assert_eq!(position.commissions(), vec![Money::from("2 USD")]); + assert_eq!(position.total_pnl(last), Money::from("13 USD")); assert_eq!(format!("{position}"), "Position(FLAT AUD/USD.SIM, id=P-1)"); } @@ -973,7 +913,7 @@ mod tests { &order1, &audusd_sim, None, - Some(PositionId::new("P-19700101-000000-001-001-1").unwrap()), + Some(PositionId::new("P-19700101-000000-001-001-1")), Some(Price::from("1.0")), None, None, @@ -981,13 +921,13 @@ mod tests { None, None, ); - let mut position = Position::new(&audusd_sim, fill1.into()).unwrap(); + let mut position = Position::new(&audusd_sim, fill1.into()); // create closing from order from different venue but same strategy let fill2 = TestOrderEventStubs::order_filled( &order2, &audusd_sim, - Some(TradeId::new("1").unwrap()), - Some(PositionId::new("P-19700101-000000-001-001-1").unwrap()), + Some(TradeId::new("1")), + Some(PositionId::new("P-19700101-000000-001-001-1")), Some(Price::from("1.00001")), Some(Quantity::from(50_000)), None, @@ -998,8 +938,8 @@ mod tests { let fill3 = TestOrderEventStubs::order_filled( &order2, &audusd_sim, - Some(TradeId::new("2").unwrap()), - Some(PositionId::new("P-19700101-000000-001-001-1").unwrap()), + Some(TradeId::new("2")), + Some(PositionId::new("P-19700101-000000-001-001-1")), Some(Price::from("1.00003")), Some(Quantity::from(50_000)), None, @@ -1025,22 +965,10 @@ mod tests { assert!(!position.is_short()); assert!(!position.is_open()); assert!(position.is_closed()); - assert_eq!( - position.commissions(), - vec![Money::from_str("6.0 USD").unwrap()] - ); - assert_eq!( - position.unrealized_pnl(last), - Money::from_str("0 USD").unwrap() - ); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("-8.0 USD").unwrap()) - ); - assert_eq!( - position.total_pnl(last), - Money::from_str("-8.0 USD").unwrap() - ); + assert_eq!(position.commissions(), vec![Money::from("6.0 USD")]); + assert_eq!(position.unrealized_pnl(last), Money::from("0 USD")); + assert_eq!(position.realized_pnl, Some(Money::from("-8.0 USD"))); + assert_eq!(position.total_pnl(last), Money::from("-8.0 USD")); assert_eq!( format!("{position}"), "Position(FLAT AUD/USD.SIM, id=P-19700101-000000-001-001-1)" @@ -1067,8 +995,8 @@ mod tests { let fill1 = TestOrderEventStubs::order_filled( &order1, &audusd_sim, - Some(TradeId::new("1").unwrap()), - Some(PositionId::new("P-19700101-000000-001-001-1").unwrap()), + Some(TradeId::new("1")), + Some(PositionId::new("P-19700101-000000-001-001-1")), Some(Price::from("1.0")), None, None, @@ -1076,12 +1004,12 @@ mod tests { None, None, ); - let mut position = Position::new(&audusd_sim, fill1.into()).unwrap(); + let mut position = Position::new(&audusd_sim, fill1.into()); let fill2 = TestOrderEventStubs::order_filled( &order2, &audusd_sim, - Some(TradeId::new("2").unwrap()), - Some(PositionId::new("P-19700101-000000-001-001-1").unwrap()), + Some(TradeId::new("2")), + Some(PositionId::new("P-19700101-000000-001-001-1")), Some(Price::from("1.0")), None, None, @@ -1107,22 +1035,10 @@ mod tests { assert!(!position.is_short()); assert!(!position.is_open()); assert!(position.is_closed()); - assert_eq!( - position.commissions(), - vec![Money::from_str("4.0 USD").unwrap()] - ); - assert_eq!( - position.unrealized_pnl(last), - Money::from_str("0 USD").unwrap() - ); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("-4.0 USD").unwrap()) - ); - assert_eq!( - position.total_pnl(last), - Money::from_str("-4.0 USD").unwrap() - ); + assert_eq!(position.commissions(), vec![Money::from("4.0 USD")]); + assert_eq!(position.unrealized_pnl(last), Money::from("0 USD")); + assert_eq!(position.realized_pnl, Some(Money::from("-4.0 USD"))); + assert_eq!(position.total_pnl(last), Money::from("-4.0 USD")); assert_eq!( format!("{position}"), "Position(FLAT AUD/USD.SIM, id=P-19700101-000000-001-001-1)" @@ -1156,8 +1072,8 @@ mod tests { let fill1 = TestOrderEventStubs::order_filled( &order1, &audusd_sim, - Some(TradeId::new("1").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("1")), + Some(PositionId::new("P-123456")), Some(Price::from("1.0")), None, None, @@ -1168,8 +1084,8 @@ mod tests { let fill2 = TestOrderEventStubs::order_filled( &order2, &audusd_sim, - Some(TradeId::new("2").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("2")), + Some(PositionId::new("P-123456")), Some(Price::from("1.00001")), None, None, @@ -1180,8 +1096,8 @@ mod tests { let fill3 = TestOrderEventStubs::order_filled( &order3, &audusd_sim, - Some(TradeId::new("3").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("3")), + Some(PositionId::new("P-123456")), Some(Price::from("1.0001")), None, None, @@ -1189,7 +1105,7 @@ mod tests { None, None, ); - let mut position = Position::new(&audusd_sim, fill1.into()).unwrap(); + let mut position = Position::new(&audusd_sim, fill1.into()); let last = Price::from("1.0005"); position.apply(&fill2.into()); position.apply(&fill3.into()); @@ -1212,19 +1128,10 @@ mod tests { assert!(!position.is_open()); assert!(!position.is_long()); assert!(!position.is_short()); - assert_eq!( - position.commissions(), - vec![Money::from_str("6.0 USD").unwrap()] - ); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("13.0 USD").unwrap()) - ); - assert_eq!( - position.unrealized_pnl(last), - Money::from_str("0 USD").unwrap() - ); - assert_eq!(position.total_pnl(last), Money::from_str("13 USD").unwrap()); + assert_eq!(position.commissions(), vec![Money::from("6.0 USD")]); + assert_eq!(position.realized_pnl, Some(Money::from("13.0 USD"))); + assert_eq!(position.unrealized_pnl(last), Money::from("0 USD")); + assert_eq!(position.total_pnl(last), Money::from("13 USD")); assert_eq!( format!("{position}"), "Position(FLAT AUD/USD.SIM, id=P-123456)" @@ -1247,8 +1154,8 @@ mod tests { let fill1 = TestOrderEventStubs::order_filled( &order1, ðusdt, - Some(TradeId::new("1").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("1")), + Some(PositionId::new("P-123456")), Some(price1), None, None, @@ -1256,7 +1163,7 @@ mod tests { None, None, ); - let mut position = Position::new(ðusdt, fill1.into()).unwrap(); + let mut position = Position::new(ðusdt, fill1.into()); let quantity2 = Quantity::from(17); let order2 = TestOrderStubs::market_order( currency_pair_ethusdt.id, @@ -1270,8 +1177,8 @@ mod tests { let fill2 = TestOrderEventStubs::order_filled( &order2, ðusdt, - Some(TradeId::new("2").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("2")), + Some(PositionId::new("P-123456")), Some(price2), None, None, @@ -1281,10 +1188,7 @@ mod tests { ); position.apply(&fill2.into()); assert_eq!(position.quantity, Quantity::from(29)); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("-0.28830000 USDT").unwrap()) - ); + assert_eq!(position.realized_pnl, Some(Money::from("-0.28830000 USDT"))); assert_eq!(position.avg_px_open, 99.413_793_103_448_27); let quantity3 = Quantity::from(9); let order3 = TestOrderStubs::market_order( @@ -1299,8 +1203,8 @@ mod tests { let fill3 = TestOrderEventStubs::order_filled( &order3, ðusdt, - Some(TradeId::new("3").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("3")), + Some(PositionId::new("P-123456")), Some(price3), None, None, @@ -1320,8 +1224,8 @@ mod tests { let fill4 = TestOrderEventStubs::order_filled( &order4, ðusdt, - Some(TradeId::new("4").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("4")), + Some(PositionId::new("P-123456")), Some(price4), None, None, @@ -1346,8 +1250,8 @@ mod tests { let fill5 = TestOrderEventStubs::order_filled( &order5, ðusdt, - Some(TradeId::new("5").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("5")), + Some(PositionId::new("P-123456")), Some(price5), None, None, @@ -1376,8 +1280,8 @@ mod tests { let fill1 = TestOrderEventStubs::order_filled( &order, &audusd_sim, - Some(TradeId::new("5").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("5")), + Some(PositionId::new("P-123456")), Some(Price::from("1.00001")), None, None, @@ -1385,7 +1289,7 @@ mod tests { Some(UnixNanos::from(1_000_000_000)), None, ); - let mut position = Position::new(&audusd_sim, fill1.into()).unwrap(); + let mut position = Position::new(&audusd_sim, fill1.into()); let fill2 = OrderFilled::new( order.trader_id(), @@ -1393,9 +1297,7 @@ mod tests { order.instrument_id(), order.client_order_id(), VenueOrderId::from("2"), - order - .account_id() - .unwrap_or(AccountId::new("SIM-001").unwrap()), + order.account_id().unwrap_or(AccountId::new("SIM-001")), TradeId::from("2"), OrderSide::Sell, OrderType::Market, @@ -1420,9 +1322,7 @@ mod tests { order.instrument_id(), order.client_order_id(), VenueOrderId::from("2"), - order - .account_id() - .unwrap_or(AccountId::new("SIM-001").unwrap()), + order.account_id().unwrap_or(AccountId::new("SIM-001")), TradeId::from("3"), OrderSide::Buy, OrderType::Market, @@ -1460,19 +1360,10 @@ mod tests { assert!(position.is_open()); assert!(!position.is_closed()); assert_eq!(position.realized_return, 0.0); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("0 USD").unwrap()) - ); - assert_eq!( - position.unrealized_pnl(last), - Money::from_str("27 USD").unwrap() - ); - assert_eq!(position.total_pnl(last), Money::from_str("27 USD").unwrap()); - assert_eq!( - position.commissions(), - vec![Money::from_str("0 USD").unwrap()] - ); + assert_eq!(position.realized_pnl, Some(Money::from("0 USD"))); + assert_eq!(position.unrealized_pnl(last), Money::from("27 USD")); + assert_eq!(position.total_pnl(last), Money::from("27 USD")); + assert_eq!(position.commissions(), vec![Money::from("0 USD")]); assert_eq!( format!("{position}"), "Position(LONG 150_000 AUD/USD.SIM, id=P-123456)" @@ -1506,7 +1397,7 @@ mod tests { None, None, ); - let mut position = Position::new(&btcusdt, fill1.into()).unwrap(); + let mut position = Position::new(&btcusdt, fill1.into()); let order2 = TestOrderStubs::market_order( currency_pair_btcusdt.id, OrderSide::Buy, @@ -1532,7 +1423,7 @@ mod tests { assert_eq!(position.quantity, Quantity::from(29)); assert_eq!( position.realized_pnl, - Some(Money::from_str("-289.98300000 USDT").unwrap()) + Some(Money::from("-289.98300000 USDT")) ); assert_eq!(position.avg_px_open, 9_999.413_793_103_447); let order3 = TestOrderStubs::market_order( @@ -1561,7 +1452,7 @@ mod tests { assert_eq!(position.quantity, Quantity::from(20)); assert_eq!( position.realized_pnl, - Some(Money::from_str("-365.71613793 USDT").unwrap()) + Some(Money::from("-365.71613793 USDT")) ); assert_eq!(position.avg_px_open, 9_999.413_793_103_447); let order4 = TestOrderStubs::market_order( @@ -1590,7 +1481,7 @@ mod tests { assert_eq!(position.quantity, Quantity::from(23)); assert_eq!( position.realized_pnl, - Some(Money::from_str("-395.72513793 USDT").unwrap()) + Some(Money::from("-395.72513793 USDT")) ); assert_eq!(position.avg_px_open, 9_999.881_559_220_39); let order5 = TestOrderStubs::market_order( @@ -1619,7 +1510,7 @@ mod tests { assert_eq!(position.quantity, Quantity::from(19)); assert_eq!( position.realized_pnl, - Some(Money::from_str("-415.27137481 USDT").unwrap()) + Some(Money::from("-415.27137481 USDT")) ); assert_eq!(position.avg_px_open, 9_999.881_559_220_39); assert_eq!( @@ -1652,7 +1543,7 @@ mod tests { None, None, ); - let position = Position::new(&btcusdt, fill.into()).unwrap(); + let position = Position::new(&btcusdt, fill.into()); let result = position.calculate_pnl(10500.0, 10500.0, Quantity::from("100000.0")); assert_eq!(result, Money::from("0 USDT")); } @@ -1681,7 +1572,7 @@ mod tests { None, None, ); - let position = Position::new(&btcusdt, fill.into()).unwrap(); + let position = Position::new(&btcusdt, fill.into()); let pnl = position.calculate_pnl(10500.0, 10510.0, Quantity::from("12.0")); assert_eq!(pnl, Money::from("120 USDT")); assert_eq!(position.realized_pnl, Some(Money::from("-126 USDT"))); @@ -1720,7 +1611,7 @@ mod tests { None, None, ); - let position = Position::new(&btcusdt, fill.into()).unwrap(); + let position = Position::new(&btcusdt, fill.into()); let pnl = position.calculate_pnl(10500.0, 10480.5, Quantity::from("10.0")); assert_eq!(pnl, Money::from("-195 USDT")); assert_eq!(position.realized_pnl, Some(Money::from("-126 USDT"))); @@ -1759,7 +1650,7 @@ mod tests { None, None, ); - let position = Position::new(&btcusdt, fill.into()).unwrap(); + let position = Position::new(&btcusdt, fill.into()); let pnl = position.calculate_pnl(10500.0, 10390.0, Quantity::from("10.15")); assert_eq!(pnl, Money::from("1116.5 USDT")); assert_eq!( @@ -1798,7 +1689,7 @@ mod tests { None, None, ); - let position = Position::new(&btcusdt, fill.into()).unwrap(); + let position = Position::new(&btcusdt, fill.into()); let pnl = position.calculate_pnl(10500.0, 10670.5, Quantity::from("10.0")); assert_eq!(pnl, Money::from("-1705 USDT")); assert_eq!( @@ -1842,7 +1733,7 @@ mod tests { None, None, ); - let position = Position::new(&xbtusd_bitmex, fill.into()).unwrap(); + let position = Position::new(&xbtusd_bitmex, fill.into()); let pnl = position.calculate_pnl(10000.0, 11000.0, Quantity::from("100000.0")); assert_eq!(pnl, Money::from("-0.90909091 BTC")); assert_eq!( @@ -1885,7 +1776,7 @@ mod tests { None, None, ); - let position = Position::new(ðusdt_bitmex, fill.into()).unwrap(); + let position = Position::new(ðusdt_bitmex, fill.into()); assert_eq!( position.unrealized_pnl(Price::from("370.00")), @@ -1920,8 +1811,8 @@ mod tests { let fill1 = TestOrderEventStubs::order_filled( &order1, &btcusdt, - Some(TradeId::new("1").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("1")), + Some(PositionId::new("P-123456")), Some(Price::from("10500.00")), None, None, @@ -1935,8 +1826,8 @@ mod tests { let fill2 = TestOrderEventStubs::order_filled( &order2, &btcusdt, - Some(TradeId::new("2").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("2")), + Some(PositionId::new("P-123456")), Some(Price::from("10500.00")), None, None, @@ -1944,7 +1835,7 @@ mod tests { None, None, ); - let mut position = Position::new(&btcusdt, fill1.into()).unwrap(); + let mut position = Position::new(&btcusdt, fill1.into()); position.apply(&fill2.into()); let pnl = position.unrealized_pnl(Price::from("11505.60")); assert_eq!(pnl, Money::from("4022.40000000 USDT")); @@ -1974,8 +1865,8 @@ mod tests { let fill = TestOrderEventStubs::order_filled( &order, &btcusdt, - Some(TradeId::new("1").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("1")), + Some(PositionId::new("P-123456")), Some(Price::from("10505.60")), None, None, @@ -1983,7 +1874,7 @@ mod tests { None, None, ); - let position = Position::new(&btcusdt, fill.into()).unwrap(); + let position = Position::new(&btcusdt, fill.into()); let pnl = position.unrealized_pnl(Price::from("10407.15")); assert_eq!(pnl, Money::from("582.03640000 USDT")); assert_eq!( @@ -2016,8 +1907,8 @@ mod tests { let fill = TestOrderEventStubs::order_filled( &order, &xbtusd_bitmex, - Some(TradeId::new("1").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("1")), + Some(PositionId::new("P-123456")), Some(Price::from("10500.00")), None, None, @@ -2026,7 +1917,7 @@ mod tests { None, ); - let position = Position::new(&xbtusd_bitmex, fill.into()).unwrap(); + let position = Position::new(&xbtusd_bitmex, fill.into()); let pnl = position.unrealized_pnl(Price::from("11505.60")); assert_eq!(pnl, Money::from("0.83238969 BTC")); assert_eq!(position.realized_pnl, Some(Money::from("-0.00714286 BTC"))); @@ -2053,8 +1944,8 @@ mod tests { let fill = TestOrderEventStubs::order_filled( &order, &xbtusd_bitmex, - Some(TradeId::new("1").unwrap()), - Some(PositionId::new("P-123456").unwrap()), + Some(TradeId::new("1")), + Some(PositionId::new("P-123456")), Some(Price::from("15500.00")), None, None, @@ -2062,7 +1953,7 @@ mod tests { None, None, ); - let position = Position::new(&xbtusd_bitmex, fill.into()).unwrap(); + let position = Position::new(&xbtusd_bitmex, fill.into()); let pnl = position.unrealized_pnl(Price::from("12506.65")); assert_eq!(pnl, Money::from("19.30166700 BTC")); @@ -2102,7 +1993,7 @@ mod tests { None, None, ); - let position = Position::new(&audusd_sim, fill.into()).unwrap(); + let position = Position::new(&audusd_sim, fill.into()); assert_eq!(position.signed_qty, expected); } @@ -2112,11 +2003,8 @@ mod tests { let mut fill = OrderFilled::default(); fill.position_id = Some(PositionId::from("1")); - let position = Position::new(&audusd_sim, fill).unwrap(); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("0 USD").unwrap()) - ); + let position = Position::new(&audusd_sim, fill); + assert_eq!(position.realized_pnl, Some(Money::from("0 USD"))); } #[rstest] @@ -2124,12 +2012,9 @@ mod tests { let audusd_sim = InstrumentAny::CurrencyPair(audusd_sim); let mut fill = OrderFilled::default(); fill.position_id = Some(PositionId::from("1")); - fill.commission = Some(Money::from_str("0 USD").unwrap()); + fill.commission = Some(Money::from("0 USD")); - let position = Position::new(&audusd_sim, fill).unwrap(); - assert_eq!( - position.realized_pnl, - Some(Money::from_str("0 USD").unwrap()) - ); + let position = Position::new(&audusd_sim, fill); + assert_eq!(position.realized_pnl, Some(Money::from("0 USD"))); } } diff --git a/nautilus_core/model/src/python/data/bar.rs b/nautilus_core/model/src/python/data/bar.rs index aef4acb4bb4e..8be4cdd2e78f 100644 --- a/nautilus_core/model/src/python/data/bar.rs +++ b/nautilus_core/model/src/python/data/bar.rs @@ -139,24 +139,24 @@ impl Bar { let open_py: &PyAny = obj.getattr("open")?; let price_prec: u8 = open_py.getattr("precision")?.extract()?; let open_raw: i64 = open_py.getattr("raw")?.extract()?; - let open = Price::from_raw(open_raw, price_prec).map_err(to_pyvalue_err)?; + let open = Price::from_raw(open_raw, price_prec); let high_py: &PyAny = obj.getattr("high")?; let high_raw: i64 = high_py.getattr("raw")?.extract()?; - let high = Price::from_raw(high_raw, price_prec).map_err(to_pyvalue_err)?; + let high = Price::from_raw(high_raw, price_prec); let low_py: &PyAny = obj.getattr("low")?; let low_raw: i64 = low_py.getattr("raw")?.extract()?; - let low = Price::from_raw(low_raw, price_prec).map_err(to_pyvalue_err)?; + let low = Price::from_raw(low_raw, price_prec); let close_py: &PyAny = obj.getattr("close")?; let close_raw: i64 = close_py.getattr("raw")?.extract()?; - let close = Price::from_raw(close_raw, price_prec).map_err(to_pyvalue_err)?; + let close = Price::from_raw(close_raw, price_prec); let volume_py: &PyAny = obj.getattr("volume")?; let volume_raw: u64 = volume_py.getattr("raw")?.extract()?; let volume_prec: u8 = volume_py.getattr("precision")?.extract()?; - let volume = Quantity::from_raw(volume_raw, volume_prec).map_err(to_pyvalue_err)?; + let volume = Quantity::from_raw(volume_raw, volume_prec); let ts_event: u64 = obj.getattr("ts_event")?.extract()?; let ts_init: u64 = obj.getattr("ts_init")?.extract()?; diff --git a/nautilus_core/model/src/python/data/delta.rs b/nautilus_core/model/src/python/data/delta.rs index 02f0f9ffd103..0567bd0bd7b9 100644 --- a/nautilus_core/model/src/python/data/delta.rs +++ b/nautilus_core/model/src/python/data/delta.rs @@ -67,12 +67,12 @@ impl OrderBookDelta { let price_py: &PyAny = order_pyobject.getattr("price")?; let price_raw: i64 = price_py.getattr("raw")?.extract()?; let price_prec: u8 = price_py.getattr("precision")?.extract()?; - let price = Price::from_raw(price_raw, price_prec).map_err(to_pyvalue_err)?; + let price = Price::from_raw(price_raw, price_prec); let size_py: &PyAny = order_pyobject.getattr("size")?; let size_raw: u64 = size_py.getattr("raw")?.extract()?; let size_prec: u8 = size_py.getattr("precision")?.extract()?; - let size = Quantity::from_raw(size_raw, size_prec).map_err(to_pyvalue_err)?; + let size = Quantity::from_raw(size_raw, size_prec); let order_id: OrderId = order_pyobject.getattr("order_id")?.extract()?; BookOrder { diff --git a/nautilus_core/model/src/python/data/depth.rs b/nautilus_core/model/src/python/data/depth.rs index 2743b8940ccd..61265227b4c7 100644 --- a/nautilus_core/model/src/python/data/depth.rs +++ b/nautilus_core/model/src/python/data/depth.rs @@ -193,8 +193,8 @@ impl OrderBookDepth10 { for order in bids.iter_mut().take(DEPTH10_LEN) { *order = BookOrder::new( OrderSide::Buy, - Price::new(price, 2).unwrap(), - Quantity::new(quantity, 0).unwrap(), + Price::new(price, 2), + Quantity::new(quantity, 0), order_id, ); @@ -211,8 +211,8 @@ impl OrderBookDepth10 { for order in asks.iter_mut().take(DEPTH10_LEN) { *order = BookOrder::new( OrderSide::Sell, - Price::new(price, 2).unwrap(), - Quantity::new(quantity, 0).unwrap(), + Price::new(price, 2), + Quantity::new(quantity, 0), order_id, ); diff --git a/nautilus_core/model/src/python/data/quote.rs b/nautilus_core/model/src/python/data/quote.rs index e09ee61fd2ae..3f5453c76811 100644 --- a/nautilus_core/model/src/python/data/quote.rs +++ b/nautilus_core/model/src/python/data/quote.rs @@ -49,22 +49,22 @@ impl QuoteTick { let bid_price_py: &PyAny = obj.getattr("bid_price")?.extract()?; let bid_price_raw: i64 = bid_price_py.getattr("raw")?.extract()?; let bid_price_prec: u8 = bid_price_py.getattr("precision")?.extract()?; - let bid_price = Price::from_raw(bid_price_raw, bid_price_prec).map_err(to_pyvalue_err)?; + let bid_price = Price::from_raw(bid_price_raw, bid_price_prec); let ask_price_py: &PyAny = obj.getattr("ask_price")?.extract()?; let ask_price_raw: i64 = ask_price_py.getattr("raw")?.extract()?; let ask_price_prec: u8 = ask_price_py.getattr("precision")?.extract()?; - let ask_price = Price::from_raw(ask_price_raw, ask_price_prec).map_err(to_pyvalue_err)?; + let ask_price = Price::from_raw(ask_price_raw, ask_price_prec); let bid_size_py: &PyAny = obj.getattr("bid_size")?.extract()?; let bid_size_raw: u64 = bid_size_py.getattr("raw")?.extract()?; let bid_size_prec: u8 = bid_size_py.getattr("precision")?.extract()?; - let bid_size = Quantity::from_raw(bid_size_raw, bid_size_prec).map_err(to_pyvalue_err)?; + let bid_size = Quantity::from_raw(bid_size_raw, bid_size_prec); let ask_size_py: &PyAny = obj.getattr("ask_size")?.extract()?; let ask_size_raw: u64 = ask_size_py.getattr("raw")?.extract()?; let ask_size_prec: u8 = ask_size_py.getattr("precision")?.extract()?; - let ask_size = Quantity::from_raw(ask_size_raw, ask_size_prec).map_err(to_pyvalue_err)?; + let ask_size = Quantity::from_raw(ask_size_raw, ask_size_prec); let ts_event: u64 = obj.getattr("ts_event")?.extract()?; let ts_init: u64 = obj.getattr("ts_init")?.extract()?; @@ -134,10 +134,10 @@ impl QuoteTick { let ts_init: u64 = tuple.10.extract()?; self.instrument_id = InstrumentId::from_str(instrument_id_str).map_err(to_pyvalue_err)?; - self.bid_price = Price::from_raw(bid_price_raw, bid_price_prec).map_err(to_pyvalue_err)?; - self.ask_price = Price::from_raw(ask_price_raw, ask_price_prec).map_err(to_pyvalue_err)?; - self.bid_size = Quantity::from_raw(bid_size_raw, bid_size_prec).map_err(to_pyvalue_err)?; - self.ask_size = Quantity::from_raw(ask_size_raw, ask_size_prec).map_err(to_pyvalue_err)?; + self.bid_price = Price::from_raw(bid_price_raw, bid_price_prec); + self.ask_price = Price::from_raw(ask_price_raw, ask_price_prec); + self.bid_size = Quantity::from_raw(bid_size_raw, bid_size_prec); + self.ask_size = Quantity::from_raw(ask_size_raw, ask_size_prec); self.ts_event = ts_event.into(); self.ts_init = ts_init.into(); @@ -295,10 +295,10 @@ impl QuoteTick { ) -> PyResult { Self::new( instrument_id, - Price::from_raw(bid_price_raw, bid_price_prec).map_err(to_pyvalue_err)?, - Price::from_raw(ask_price_raw, ask_price_prec).map_err(to_pyvalue_err)?, - Quantity::from_raw(bid_size_raw, bid_size_prec).map_err(to_pyvalue_err)?, - Quantity::from_raw(ask_size_raw, ask_size_prec).map_err(to_pyvalue_err)?, + Price::from_raw(bid_price_raw, bid_price_prec), + Price::from_raw(ask_price_raw, ask_price_prec), + Quantity::from_raw(bid_size_raw, bid_size_prec), + Quantity::from_raw(ask_size_raw, ask_size_prec), ts_event.into(), ts_init.into(), ) diff --git a/nautilus_core/model/src/python/data/trade.rs b/nautilus_core/model/src/python/data/trade.rs index f9c168b63e2c..0781c95b63f4 100644 --- a/nautilus_core/model/src/python/data/trade.rs +++ b/nautilus_core/model/src/python/data/trade.rs @@ -49,20 +49,20 @@ impl TradeTick { let price_py: &PyAny = obj.getattr("price")?.extract()?; let price_raw: i64 = price_py.getattr("raw")?.extract()?; let price_prec: u8 = price_py.getattr("precision")?.extract()?; - let price = Price::from_raw(price_raw, price_prec).map_err(to_pyvalue_err)?; + let price = Price::from_raw(price_raw, price_prec); let size_py: &PyAny = obj.getattr("size")?.extract()?; let size_raw: u64 = size_py.getattr("raw")?.extract()?; let size_prec: u8 = size_py.getattr("precision")?.extract()?; - let size = Quantity::from_raw(size_raw, size_prec).map_err(to_pyvalue_err)?; + let size = Quantity::from_raw(size_raw, size_prec); let aggressor_side_obj: &PyAny = obj.getattr("aggressor_side")?.extract()?; let aggressor_side_u8 = aggressor_side_obj.getattr("value")?.extract()?; let aggressor_side = AggressorSide::from_u8(aggressor_side_u8).unwrap(); let trade_id_obj: &PyAny = obj.getattr("trade_id")?.extract()?; - let trade_id_str = trade_id_obj.getattr("value")?.extract()?; - let trade_id = TradeId::from_str(trade_id_str).map_err(to_pyvalue_err)?; + let trade_id_str: String = trade_id_obj.getattr("value")?.extract()?; + let trade_id = TradeId::from(trade_id_str.as_str()); let ts_event: u64 = obj.getattr("ts_event")?.extract()?; let ts_init: u64 = obj.getattr("ts_init")?.extract()?; @@ -120,15 +120,15 @@ impl TradeTick { let size_raw = tuple.3.extract()?; let size_prec = tuple.4.extract()?; let aggressor_side_u8 = tuple.5.extract()?; - let trade_id_str = tuple.6.extract()?; + let trade_id_str: String = tuple.6.extract()?; let ts_event: u64 = tuple.7.extract()?; let ts_init: u64 = tuple.8.extract()?; self.instrument_id = InstrumentId::from_str(instrument_id_str).map_err(to_pyvalue_err)?; - self.price = Price::from_raw(price_raw, price_prec).map_err(to_pyvalue_err)?; - self.size = Quantity::from_raw(size_raw, size_prec).map_err(to_pyvalue_err)?; + self.price = Price::from_raw(price_raw, price_prec); + self.size = Quantity::from_raw(size_raw, size_prec); self.aggressor_side = AggressorSide::from_u8(aggressor_side_u8).unwrap(); - self.trade_id = TradeId::from_str(trade_id_str).map_err(to_pyvalue_err)?; + self.trade_id = TradeId::from(trade_id_str.as_str()); self.ts_event = ts_event.into(); self.ts_init = ts_init.into(); diff --git a/nautilus_core/model/src/python/events/account/state.rs b/nautilus_core/model/src/python/events/account/state.rs index cad9cc32dded..9db1183c9308 100644 --- a/nautilus_core/model/src/python/events/account/state.rs +++ b/nautilus_core/model/src/python/events/account/state.rs @@ -136,7 +136,7 @@ impl AccountState { let ts_event: u64 = dict.get_item("ts_event")?.unwrap().extract()?; let ts_init: u64 = dict.get_item("ts_init")?.unwrap().extract()?; let account = Self::new( - AccountId::from_str(account_id).unwrap(), + AccountId::from(account_id), AccountType::from_str(account_type).unwrap(), balances, margins, diff --git a/nautilus_core/model/src/python/identifiers/instrument_id.rs b/nautilus_core/model/src/python/identifiers/instrument_id.rs index 4ca9d9dd4bba..ef1d0db61070 100644 --- a/nautilus_core/model/src/python/identifiers/instrument_id.rs +++ b/nautilus_core/model/src/python/identifiers/instrument_id.rs @@ -31,14 +31,14 @@ use crate::identifiers::{InstrumentId, Symbol, Venue}; #[pymethods] impl InstrumentId { #[new] - fn py_new(symbol: Symbol, venue: Venue) -> PyResult { - Ok(Self::new(symbol, venue)) + fn py_new(symbol: Symbol, venue: Venue) -> Self { + Self::new(symbol, venue) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { let tuple: (&PyString, &PyString) = state.extract(py)?; - self.symbol = Symbol::new(tuple.0.extract()?).map_err(to_pyvalue_err)?; - self.venue = Venue::new(tuple.1.extract()?).map_err(to_pyvalue_err)?; + self.symbol = Symbol::new(tuple.0.extract()?); + self.venue = Venue::new(tuple.1.extract()?); Ok(()) } diff --git a/nautilus_core/model/src/python/identifiers/trade_id.rs b/nautilus_core/model/src/python/identifiers/trade_id.rs index 7f78e605cf00..d5b7b617dbaa 100644 --- a/nautilus_core/model/src/python/identifiers/trade_id.rs +++ b/nautilus_core/model/src/python/identifiers/trade_id.rs @@ -32,8 +32,8 @@ use crate::identifiers::trade_id::TradeId; #[pymethods] impl TradeId { #[new] - fn py_new(value: &str) -> PyResult { - Self::new(value).map_err(to_pyvalue_err) + fn py_new(value: &str) -> Self { + Self::new(value) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { @@ -61,8 +61,8 @@ impl TradeId { } #[staticmethod] - fn _safe_constructor() -> PyResult { - Ok(Self::from_str("NULL").unwrap()) // Safe default + fn _safe_constructor() -> Self { + Self::from("NULL") } fn __richcmp__(&self, other: PyObject, op: CompareOp, py: Python<'_>) -> Py { @@ -98,8 +98,8 @@ impl TradeId { #[staticmethod] #[pyo3(name = "from_str")] - fn py_from_str(value: &str) -> PyResult { - Self::new(value).map_err(to_pyvalue_err) + fn py_from_str(value: &str) -> Self { + Self::new(value) } } diff --git a/nautilus_core/model/src/python/instruments/crypto_future.rs b/nautilus_core/model/src/python/instruments/crypto_future.rs index d931c696684e..79b7a22d7c70 100644 --- a/nautilus_core/model/src/python/instruments/crypto_future.rs +++ b/nautilus_core/model/src/python/instruments/crypto_future.rs @@ -58,7 +58,7 @@ impl CryptoFuture { min_notional: Option, max_price: Option, min_price: Option, - ) -> PyResult { + ) -> Self { Self::new( id, raw_symbol, @@ -86,7 +86,6 @@ impl CryptoFuture { ts_event.into(), ts_init.into(), ) - .map_err(to_pyvalue_err) } fn __hash__(&self) -> isize { diff --git a/nautilus_core/model/src/python/instruments/crypto_perpetual.rs b/nautilus_core/model/src/python/instruments/crypto_perpetual.rs index 6530f4711a84..30094ecbe53e 100644 --- a/nautilus_core/model/src/python/instruments/crypto_perpetual.rs +++ b/nautilus_core/model/src/python/instruments/crypto_perpetual.rs @@ -56,7 +56,7 @@ impl CryptoPerpetual { min_notional: Option, max_price: Option, min_price: Option, - ) -> PyResult { + ) -> Self { Self::new( id, raw_symbol, @@ -82,7 +82,6 @@ impl CryptoPerpetual { ts_event.into(), ts_init.into(), ) - .map_err(to_pyvalue_err) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/instruments/currency_pair.rs b/nautilus_core/model/src/python/instruments/currency_pair.rs index f98d247ad1bc..36fabfc0f73b 100644 --- a/nautilus_core/model/src/python/instruments/currency_pair.rs +++ b/nautilus_core/model/src/python/instruments/currency_pair.rs @@ -54,7 +54,7 @@ impl CurrencyPair { min_notional: Option, max_price: Option, min_price: Option, - ) -> PyResult { + ) -> Self { Self::new( id, raw_symbol, @@ -78,7 +78,6 @@ impl CurrencyPair { ts_event.into(), ts_init.into(), ) - .map_err(to_pyvalue_err) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/instruments/equity.rs b/nautilus_core/model/src/python/instruments/equity.rs index 89e215d08b2f..d0e81d74d654 100644 --- a/nautilus_core/model/src/python/instruments/equity.rs +++ b/nautilus_core/model/src/python/instruments/equity.rs @@ -51,7 +51,7 @@ impl Equity { min_quantity: Option, max_price: Option, min_price: Option, - ) -> PyResult { + ) -> Self { Self::new( id, raw_symbol, @@ -71,7 +71,6 @@ impl Equity { ts_event.into(), ts_init.into(), ) - .map_err(to_pyvalue_err) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/instruments/futures_contract.rs b/nautilus_core/model/src/python/instruments/futures_contract.rs index 14d9681baed7..b21c768db08b 100644 --- a/nautilus_core/model/src/python/instruments/futures_contract.rs +++ b/nautilus_core/model/src/python/instruments/futures_contract.rs @@ -55,7 +55,7 @@ impl FuturesContract { max_price: Option, min_price: Option, exchange: Option, - ) -> PyResult { + ) -> Self { Self::new( id, raw_symbol, @@ -78,7 +78,6 @@ impl FuturesContract { ts_event.into(), ts_init.into(), ) - .map_err(to_pyvalue_err) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/instruments/futures_spread.rs b/nautilus_core/model/src/python/instruments/futures_spread.rs index ba061dec7385..86c8970afa64 100644 --- a/nautilus_core/model/src/python/instruments/futures_spread.rs +++ b/nautilus_core/model/src/python/instruments/futures_spread.rs @@ -56,7 +56,7 @@ impl FuturesSpread { max_price: Option, min_price: Option, exchange: Option, - ) -> PyResult { + ) -> Self { Self::new( id, raw_symbol, @@ -80,7 +80,6 @@ impl FuturesSpread { ts_event.into(), ts_init.into(), ) - .map_err(to_pyvalue_err) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/instruments/options_contract.rs b/nautilus_core/model/src/python/instruments/options_contract.rs index 68a2eb8a9ab9..af62055d2136 100644 --- a/nautilus_core/model/src/python/instruments/options_contract.rs +++ b/nautilus_core/model/src/python/instruments/options_contract.rs @@ -57,7 +57,7 @@ impl OptionsContract { max_price: Option, min_price: Option, exchange: Option, - ) -> PyResult { + ) -> Self { Self::new( id, raw_symbol, @@ -82,7 +82,6 @@ impl OptionsContract { ts_event.into(), ts_init.into(), ) - .map_err(to_pyvalue_err) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/instruments/options_spread.rs b/nautilus_core/model/src/python/instruments/options_spread.rs index a7f5c59abebb..58f8ec74b417 100644 --- a/nautilus_core/model/src/python/instruments/options_spread.rs +++ b/nautilus_core/model/src/python/instruments/options_spread.rs @@ -56,7 +56,7 @@ impl OptionsSpread { max_price: Option, min_price: Option, exchange: Option, - ) -> PyResult { + ) -> Self { Self::new( id, raw_symbol, @@ -80,7 +80,6 @@ impl OptionsSpread { ts_event.into(), ts_init.into(), ) - .map_err(to_pyvalue_err) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/macros.rs b/nautilus_core/model/src/python/macros.rs index 71f0520b0107..cc1413f2557c 100644 --- a/nautilus_core/model/src/python/macros.rs +++ b/nautilus_core/model/src/python/macros.rs @@ -21,11 +21,8 @@ macro_rules! identifier_for_python { #[pymethods] impl $ty { #[new] - fn py_new(value: &str) -> PyResult { - match <$ty>::new(value) { - Ok(instance) => Ok(instance), - Err(e) => Err(to_pyvalue_err(e)), - } + fn py_new(value: &str) -> Self { + <$ty>::new(value) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { @@ -47,7 +44,7 @@ macro_rules! identifier_for_python { #[staticmethod] fn _safe_constructor() -> PyResult { - Ok(<$ty>::from_str("NULL").unwrap()) // Safe default + Ok(<$ty>::from("NULL")) // Safe default } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { @@ -85,8 +82,8 @@ macro_rules! identifier_for_python { #[staticmethod] #[pyo3(name = "from_str")] - fn py_from_str(value: &str) -> PyResult { - Self::from_str(value).map_err(to_pyvalue_err) + fn py_from_str(value: &str) -> Self { + Self::from(value) } } }; diff --git a/nautilus_core/model/src/python/orders/limit.rs b/nautilus_core/model/src/python/orders/limit.rs index 2b07b87eab23..08df1aafdde7 100644 --- a/nautilus_core/model/src/python/orders/limit.rs +++ b/nautilus_core/model/src/python/orders/limit.rs @@ -451,7 +451,7 @@ impl LimitOrder { x.and_then(|inner| { let extracted_str = inner.extract::<&str>(); match extracted_str { - Ok(item) => item.parse::().ok(), + Ok(item) => Some(OrderListId::from(item)), Err(_) => None, } }) @@ -462,7 +462,7 @@ impl LimitOrder { match extracted_str { Ok(item) => Some( item.iter() - .map(|x| x.parse::().unwrap()) + .map(|x| ClientOrderId::from(x.as_str())) .collect(), ), Err(_) => None, @@ -473,7 +473,7 @@ impl LimitOrder { x.and_then(|inner| { let extracted_str = inner.extract::<&str>(); match extracted_str { - Ok(item) => item.parse::().ok(), + Ok(item) => Some(ClientOrderId::from(item)), Err(_) => None, } }) @@ -482,7 +482,7 @@ impl LimitOrder { x.and_then(|inner| { let extracted_str = inner.extract::<&str>(); match extracted_str { - Ok(item) => item.parse::().ok(), + Ok(item) => Some(ExecAlgorithmId::from(item)), Err(_) => None, } }) @@ -500,7 +500,7 @@ impl LimitOrder { x.and_then(|inner| { let extracted_str = inner.extract::<&str>(); match extracted_str { - Ok(item) => item.parse::().ok(), + Ok(item) => Some(ClientOrderId::from(item)), Err(_) => None, } }) diff --git a/nautilus_core/model/src/python/orders/market.rs b/nautilus_core/model/src/python/orders/market.rs index d2963aa1681f..c9d87ad5630c 100644 --- a/nautilus_core/model/src/python/orders/market.rs +++ b/nautilus_core/model/src/python/orders/market.rs @@ -457,7 +457,7 @@ impl MarketOrder { x.and_then(|inner| { let extracted_str = inner.extract::<&str>(); match extracted_str { - Ok(item) => item.parse::().ok(), + Ok(item) => Some(OrderListId::from(item)), Err(_) => None, } }) @@ -468,7 +468,7 @@ impl MarketOrder { match extracted_str { Ok(item) => Some( item.iter() - .map(|x| x.parse::().unwrap()) + .map(|x| ClientOrderId::from(x.as_str())) .collect(), ), Err(_) => None, @@ -479,7 +479,7 @@ impl MarketOrder { x.and_then(|inner| { let extracted_str = inner.extract::<&str>(); match extracted_str { - Ok(item) => item.parse::().ok(), + Ok(item) => Some(ClientOrderId::from(item)), Err(_) => None, } }) @@ -488,7 +488,7 @@ impl MarketOrder { x.and_then(|inner| { let extracted_str = inner.extract::<&str>(); match extracted_str { - Ok(item) => item.parse::().ok(), + Ok(item) => Some(ExecAlgorithmId::from(item)), Err(_) => None, } }) @@ -506,7 +506,7 @@ impl MarketOrder { x.and_then(|inner| { let extracted_str = inner.extract::<&str>(); match extracted_str { - Ok(item) => item.parse::().ok(), + Ok(item) => Some(ClientOrderId::from(item)), Err(_) => None, } }) diff --git a/nautilus_core/model/src/python/orders/stop_limit.rs b/nautilus_core/model/src/python/orders/stop_limit.rs index 558307e586bc..083a968c67d3 100644 --- a/nautilus_core/model/src/python/orders/stop_limit.rs +++ b/nautilus_core/model/src/python/orders/stop_limit.rs @@ -561,7 +561,7 @@ impl StopLimitOrder { x.and_then(|x| { let extracted = x.extract::<&str>(); match extracted { - Ok(item) => Some(item.parse::().unwrap()), + Ok(item) => Some(OrderListId::from(item)), Err(_) => None, } }) @@ -573,7 +573,7 @@ impl StopLimitOrder { match extracted_str { Ok(item) => Some( item.iter() - .map(|x| x.parse::().unwrap()) + .map(|x| ClientOrderId::from(x.as_str())) .collect(), ), Err(_) => None, @@ -586,7 +586,7 @@ impl StopLimitOrder { x.and_then(|x| { let extracted = x.extract::<&str>(); match extracted { - Ok(item) => item.parse::().ok(), + Ok(item) => Some(ClientOrderId::from(item)), Err(_) => None, } }) @@ -598,7 +598,7 @@ impl StopLimitOrder { x.and_then(|x| { let extracted = x.extract::<&str>(); match extracted { - Ok(item) => Some(item.parse::().unwrap()), + Ok(item) => Some(ExecAlgorithmId::from(item)), Err(_) => None, } }) @@ -619,7 +619,7 @@ impl StopLimitOrder { x.and_then(|x| { let extracted = x.extract::<&str>(); match extracted { - Ok(item) => Some(item.parse::().unwrap()), + Ok(item) => Some(ClientOrderId::from(item)), Err(_) => None, } }) diff --git a/nautilus_core/model/src/python/position.rs b/nautilus_core/model/src/python/position.rs index f725b7ea4561..55cd7d30669a 100644 --- a/nautilus_core/model/src/python/position.rs +++ b/nautilus_core/model/src/python/position.rs @@ -40,7 +40,7 @@ impl Position { #[new] fn py_new(py: Python, instrument: PyObject, fill: OrderFilled) -> PyResult { let instrument_any = pyobject_to_instrument_any(py, instrument)?; - Ok(Self::new(&instrument_any, fill).map_err(to_pyvalue_err)?) + Ok(Self::new(&instrument_any, fill)) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/types/balance.rs b/nautilus_core/model/src/python/types/balance.rs index 9b93f05e3803..e38bbd47e516 100644 --- a/nautilus_core/model/src/python/types/balance.rs +++ b/nautilus_core/model/src/python/types/balance.rs @@ -30,8 +30,8 @@ use crate::{ #[pymethods] impl AccountBalance { #[new] - fn py_new(total: Money, locked: Money, free: Money) -> PyResult { - Self::new(total, locked, free).map_err(to_pyvalue_err) + fn py_new(total: Money, locked: Money, free: Money) -> Self { + Self::new(total, locked, free) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { @@ -63,11 +63,10 @@ impl AccountBalance { let locked: f64 = locked_str.parse::().unwrap(); let currency = Currency::from_str(currency).map_err(to_pyvalue_err)?; let account_balance = Self::new( - Money::new(total, currency).map_err(to_pyvalue_err)?, - Money::new(locked, currency).map_err(to_pyvalue_err)?, - Money::new(free, currency).map_err(to_pyvalue_err)?, - ) - .unwrap(); + Money::new(total, currency), + Money::new(locked, currency), + Money::new(free, currency), + ); Ok(account_balance) } @@ -107,8 +106,8 @@ impl AccountBalance { #[pymethods] impl MarginBalance { #[new] - fn py_new(initial: Money, maintenance: Money, instrument: InstrumentId) -> PyResult { - Self::new(initial, maintenance, instrument).map_err(to_pyvalue_err) + fn py_new(initial: Money, maintenance: Money, instrument: InstrumentId) -> Self { + Self::new(initial, maintenance, instrument) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { match op { @@ -138,11 +137,10 @@ impl MarginBalance { let instrument_id_str: &str = dict.get_item("instrument_id")?.unwrap().extract()?; let currency = Currency::from_str(currency).map_err(to_pyvalue_err)?; let account_balance = Self::new( - Money::new(initial, currency).map_err(to_pyvalue_err)?, - Money::new(maintenance, currency).map_err(to_pyvalue_err)?, - InstrumentId::from_str(instrument_id_str).unwrap(), - ) - .unwrap(); + Money::new(initial, currency), + Money::new(maintenance, currency), + InstrumentId::from(instrument_id_str), + ); Ok(account_balance) } diff --git a/nautilus_core/model/src/python/types/currency.rs b/nautilus_core/model/src/python/types/currency.rs index 0f76df810a75..711856f8dead 100644 --- a/nautilus_core/model/src/python/types/currency.rs +++ b/nautilus_core/model/src/python/types/currency.rs @@ -35,8 +35,8 @@ impl Currency { iso4217: u16, name: &str, currency_type: CurrencyType, - ) -> PyResult { - Self::new(code, precision, iso4217, name, currency_type).map_err(to_pyvalue_err) + ) -> Self { + Self::new(code, precision, iso4217, name, currency_type) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { @@ -149,9 +149,7 @@ impl Currency { if strict { Err(to_pyvalue_err(e)) } else { - // SAFETY: Unwrap safe as using known values - let new_crypto = Self::new(value, 8, 0, value, CurrencyType::Crypto).unwrap(); - Ok(new_crypto) + Ok(Self::new(value, 8, 0, value, CurrencyType::Crypto)) } } } diff --git a/nautilus_core/model/src/python/types/money.rs b/nautilus_core/model/src/python/types/money.rs index b4520e016371..23128e999ac1 100644 --- a/nautilus_core/model/src/python/types/money.rs +++ b/nautilus_core/model/src/python/types/money.rs @@ -34,8 +34,8 @@ use crate::types::{currency::Currency, money::Money}; #[pymethods] impl Money { #[new] - fn py_new(value: f64, currency: Currency) -> PyResult { - Self::new(value, currency).map_err(to_pyvalue_err) + fn py_new(value: f64, currency: Currency) -> Self { + Self::new(value, currency) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { @@ -57,8 +57,8 @@ impl Money { } #[staticmethod] - fn _safe_constructor() -> PyResult { - Ok(Self::new(0.0, Currency::AUD()).unwrap()) // Safe default + fn _safe_constructor() -> Self { + Self::new(0.0, Currency::AUD()) } fn __add__(&self, other: PyObject, py: Python) -> PyResult { @@ -331,8 +331,8 @@ impl Money { #[staticmethod] #[pyo3(name = "zero")] - fn py_zero(currency: Currency) -> PyResult { - Self::new(0.0, currency).map_err(to_pyvalue_err) + fn py_zero(currency: Currency) -> Self { + Self::new(0.0, currency) } #[staticmethod] diff --git a/nautilus_core/model/src/python/types/price.rs b/nautilus_core/model/src/python/types/price.rs index 1924af521f8c..a24412c099fa 100644 --- a/nautilus_core/model/src/python/types/price.rs +++ b/nautilus_core/model/src/python/types/price.rs @@ -33,8 +33,8 @@ use crate::types::{fixed::fixed_i64_to_f64, price::Price}; #[pymethods] impl Price { #[new] - fn py_new(value: f64, precision: u8) -> PyResult { - Self::new(value, precision).map_err(to_pyvalue_err) + fn py_new(value: f64, precision: u8) -> Self { + Self::new(value, precision) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { @@ -331,21 +331,21 @@ impl Price { #[staticmethod] #[pyo3(name = "from_raw")] - fn py_from_raw(raw: i64, precision: u8) -> PyResult { - Self::from_raw(raw, precision).map_err(to_pyvalue_err) + fn py_from_raw(raw: i64, precision: u8) -> Self { + Self::from_raw(raw, precision) } #[staticmethod] #[pyo3(name = "zero")] #[pyo3(signature = (precision = 0))] - fn py_zero(precision: u8) -> PyResult { - Self::new(0.0, precision).map_err(to_pyvalue_err) + fn py_zero(precision: u8) -> Self { + Self::new(0.0, precision) } #[staticmethod] #[pyo3(name = "from_int")] - fn py_from_int(value: u64) -> PyResult { - Self::new(value as f64, 0).map_err(to_pyvalue_err) + fn py_from_int(value: u64) -> Self { + Self::new(value as f64, 0) } #[staticmethod] diff --git a/nautilus_core/model/src/python/types/quantity.rs b/nautilus_core/model/src/python/types/quantity.rs index 6611427dae06..b292b9aef2dd 100644 --- a/nautilus_core/model/src/python/types/quantity.rs +++ b/nautilus_core/model/src/python/types/quantity.rs @@ -33,8 +33,8 @@ use crate::types::quantity::Quantity; #[pymethods] impl Quantity { #[new] - fn py_new(value: f64, precision: u8) -> PyResult { - Self::new(value, precision).map_err(to_pyvalue_err) + fn py_new(value: f64, precision: u8) -> Self { + Self::new(value, precision) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { @@ -331,27 +331,27 @@ impl Quantity { #[staticmethod] #[pyo3(name = "from_raw")] - fn py_from_raw(raw: u64, precision: u8) -> PyResult { - Self::from_raw(raw, precision).map_err(to_pyvalue_err) + fn py_from_raw(raw: u64, precision: u8) -> Self { + Self::from_raw(raw, precision) } #[staticmethod] #[pyo3(name = "zero")] #[pyo3(signature = (precision = 0))] - fn py_zero(precision: u8) -> PyResult { - Self::new(0.0, precision).map_err(to_pyvalue_err) + fn py_zero(precision: u8) -> Self { + Self::new(0.0, precision) } #[staticmethod] #[pyo3(name = "from_int")] - fn py_from_int(value: u64) -> PyResult { - Self::new(value as f64, 0).map_err(to_pyvalue_err) + fn py_from_int(value: u64) -> Self { + Self::new(value as f64, 0) } #[staticmethod] #[pyo3(name = "from_str")] - fn py_from_str(value: &str) -> PyResult { - Self::from_str(value).map_err(to_pyvalue_err) + fn py_from_str(value: &str) -> Self { + Self::from(value) } #[pyo3(name = "is_zero")] diff --git a/nautilus_core/model/src/stubs.rs b/nautilus_core/model/src/stubs.rs index c6a4fba44f4c..214b8cdb00cb 100644 --- a/nautilus_core/model/src/stubs.rs +++ b/nautilus_core/model/src/stubs.rs @@ -53,9 +53,9 @@ pub fn calculate_commission( panic!("Invalid liquidity side {liquidity_side}") }; if instrument.is_inverse() && !use_quote_for_inverse.unwrap_or(false) { - Ok(Money::new(commission, instrument.base_currency().unwrap()).unwrap()) + Ok(Money::new(commission, instrument.base_currency().unwrap())) } else { - Ok(Money::new(commission, instrument.quote_currency()).unwrap()) + Ok(Money::new(commission, instrument.quote_currency())) } } @@ -81,7 +81,7 @@ pub fn test_position_long(audusd_sim: CurrencyPair) -> Position { None, None, ); - Position::new(&audusd_sim, filled.into()).unwrap() + Position::new(&audusd_sim, filled.into()) } #[fixture] @@ -106,7 +106,7 @@ pub fn test_position_short(audusd_sim: CurrencyPair) -> Position { None, None, ); - Position::new(&audusd_sim, filled.into()).unwrap() + Position::new(&audusd_sim, filled.into()) } #[must_use] @@ -146,13 +146,11 @@ pub fn stub_order_book_mbp( let price = Price::new( price_increment.mul_add(-(i as f64), top_bid_price), price_precision, - ) - .unwrap(); + ); let size = Quantity::new( size_increment.mul_add(i as f64, top_bid_size), size_precision, - ) - .unwrap(); + ); let order = BookOrder::new( OrderSide::Buy, price, @@ -167,13 +165,11 @@ pub fn stub_order_book_mbp( let price = Price::new( price_increment.mul_add(i as f64, top_ask_price), price_precision, - ) - .unwrap(); + ); let size = Quantity::new( size_increment.mul_add(i as f64, top_ask_size), size_precision, - ) - .unwrap(); + ); let order = BookOrder::new( OrderSide::Sell, price, diff --git a/nautilus_core/model/src/types/balance.rs b/nautilus_core/model/src/types/balance.rs index e3a35113e9f5..ff05386d63d4 100644 --- a/nautilus_core/model/src/types/balance.rs +++ b/nautilus_core/model/src/types/balance.rs @@ -35,16 +35,16 @@ pub struct AccountBalance { } impl AccountBalance { - pub fn new(total: Money, locked: Money, free: Money) -> anyhow::Result { + pub fn new(total: Money, locked: Money, free: Money) -> Self { assert!(total == locked + free, "Total balance is not equal to the sum of locked and free balances: {total} != {locked} + {free}" ); - Ok(Self { + Self { currency: total.currency, total, locked, free, - }) + } } } @@ -86,17 +86,13 @@ pub struct MarginBalance { } impl MarginBalance { - pub fn new( - initial: Money, - maintenance: Money, - instrument_id: InstrumentId, - ) -> anyhow::Result { - Ok(Self { + pub fn new(initial: Money, maintenance: Money, instrument_id: InstrumentId) -> Self { + Self { initial, maintenance, currency: initial.currency, instrument_id, - }) + } } } diff --git a/nautilus_core/model/src/types/currency.rs b/nautilus_core/model/src/types/currency.rs index b497ba617889..4714ded845e3 100644 --- a/nautilus_core/model/src/types/currency.rs +++ b/nautilus_core/model/src/types/currency.rs @@ -19,7 +19,7 @@ use std::{ str::FromStr, }; -use nautilus_core::correctness::check_valid_string; +use nautilus_core::correctness::{check_valid_string, FAILED}; use serde::{Deserialize, Serialize, Serializer}; use ustr::Ustr; @@ -48,18 +48,17 @@ impl Currency { iso4217: u16, name: &str, currency_type: CurrencyType, - ) -> anyhow::Result { - check_valid_string(code, "code")?; - check_valid_string(name, "name")?; - check_fixed_precision(precision)?; - - Ok(Self { + ) -> Self { + check_valid_string(code, "code").expect(FAILED); + check_valid_string(name, "name").expect(FAILED); + check_fixed_precision(precision).expect(FAILED); + Self { code: Ustr::from(code), precision, iso4217, name: Ustr::from(name), currency_type, - }) + } } pub fn register(currency: Self, overwrite: bool) -> anyhow::Result<()> { @@ -142,7 +141,9 @@ impl FromStr for Currency { impl From<&str> for Currency { fn from(input: &str) -> Self { - input.parse().unwrap() + input + .parse() + .expect("Currency string representation should be valid") } } @@ -194,20 +195,19 @@ mod tests { #[rstest] #[should_panic(expected = "code")] fn test_invalid_currency_code() { - let _ = Currency::new("", 2, 840, "United States dollar", CurrencyType::Fiat).unwrap(); + let _ = Currency::new("", 2, 840, "United States dollar", CurrencyType::Fiat); } #[rstest] #[should_panic(expected = "Condition failed: `precision` was greater than the maximum ")] fn test_invalid_precision() { // Precision out of range for fixed - let _ = Currency::new("USD", 10, 840, "United States dollar", CurrencyType::Fiat).unwrap(); + let _ = Currency::new("USD", 10, 840, "United States dollar", CurrencyType::Fiat); } #[rstest] fn test_new_for_fiat() { - let currency = - Currency::new("AUD", 2, 36, "Australian dollar", CurrencyType::Fiat).unwrap(); + let currency = Currency::new("AUD", 2, 36, "Australian dollar", CurrencyType::Fiat); assert_eq!(currency, currency); assert_eq!(currency.code.as_str(), "AUD"); assert_eq!(currency.precision, 2); @@ -218,7 +218,7 @@ mod tests { #[rstest] fn test_new_for_crypto() { - let currency = Currency::new("ETH", 8, 0, "Ether", CurrencyType::Crypto).unwrap(); + let currency = Currency::new("ETH", 8, 0, "Ether", CurrencyType::Crypto); assert_eq!(currency, currency); assert_eq!(currency.code.as_str(), "ETH"); assert_eq!(currency.precision, 8); @@ -229,10 +229,8 @@ mod tests { #[rstest] fn test_equality() { - let currency1 = - Currency::new("USD", 2, 840, "United States dollar", CurrencyType::Fiat).unwrap(); - let currency2 = - Currency::new("USD", 2, 840, "United States dollar", CurrencyType::Fiat).unwrap(); + let currency1 = Currency::new("USD", 2, 840, "United States dollar", CurrencyType::Fiat); + let currency2 = Currency::new("USD", 2, 840, "United States dollar", CurrencyType::Fiat); assert_eq!(currency1, currency2); } diff --git a/nautilus_core/model/src/types/money.rs b/nautilus_core/model/src/types/money.rs index 3715293bdbf0..e4f7287ac29b 100644 --- a/nautilus_core/model/src/types/money.rs +++ b/nautilus_core/model/src/types/money.rs @@ -21,7 +21,7 @@ use std::{ str::FromStr, }; -use nautilus_core::correctness::check_in_range_inclusive_f64; +use nautilus_core::correctness::{check_in_range_inclusive_f64, FAILED}; use rust_decimal::Decimal; use serde::{Deserialize, Deserializer, Serialize}; use thousands::Separable; @@ -51,13 +51,13 @@ pub struct Money { impl Money { /// Creates a new [`Money`] instance. - pub fn new(amount: f64, currency: Currency) -> anyhow::Result { - check_in_range_inclusive_f64(amount, MONEY_MIN, MONEY_MAX, "amount")?; + pub fn new(amount: f64, currency: Currency) -> Self { + check_in_range_inclusive_f64(amount, MONEY_MIN, MONEY_MAX, "amount").expect(FAILED); - Ok(Self { + Self { raw: f64_to_fixed_i64(amount, currency.precision), currency, - }) + } } #[must_use] @@ -113,7 +113,7 @@ impl FromStr for Money { // Parse currency let currency = Currency::from_str(parts[1]).map_err(|e: anyhow::Error| e.to_string())?; - Self::new(amount, currency).map_err(|e: anyhow::Error| e.to_string()) + Ok(Self::new(amount, currency)) } } @@ -323,8 +323,7 @@ impl<'de> Deserialize<'de> for Money { D: Deserializer<'de>, { let money_str: String = Deserialize::deserialize(deserializer)?; - Money::from_str(&money_str) - .map_err(|_| serde::de::Error::custom("Failed to parse Money amount")) + Ok(Money::from(money_str.as_str())) } } @@ -341,7 +340,7 @@ mod tests { #[rstest] fn test_debug() { - let money = Money::new(1010.12, Currency::USD()).unwrap(); + let money = Money::new(1010.12, Currency::USD()); let result = format!("{:?}", money); let expected = "Money(1010.12, USD)"; assert_eq!(result, expected); @@ -349,7 +348,7 @@ mod tests { #[rstest] fn test_display() { - let money = Money::new(1010.12, Currency::USD()).unwrap(); + let money = Money::new(1010.12, Currency::USD()); let result = format!("{money}"); let expected = "1010.12 USD"; assert_eq!(result, expected); @@ -358,15 +357,15 @@ mod tests { #[rstest] #[should_panic] fn test_money_different_currency_addition() { - let usd = Money::new(1000.0, Currency::USD()).unwrap(); - let btc = Money::new(1.0, Currency::BTC()).unwrap(); + let usd = Money::new(1000.0, Currency::USD()); + let btc = Money::new(1.0, Currency::BTC()); let _result = usd + btc; // This should panic since currencies are different } #[rstest] fn test_money_min_max_values() { - let min_money = Money::new(MONEY_MIN, Currency::USD()).unwrap(); - let max_money = Money::new(MONEY_MAX, Currency::USD()).unwrap(); + let min_money = Money::new(MONEY_MIN, Currency::USD()); + let max_money = Money::new(MONEY_MAX, Currency::USD()); assert_eq!( min_money.raw, f64_to_fixed_i64(MONEY_MIN, Currency::USD().precision) @@ -379,14 +378,14 @@ mod tests { #[rstest] fn test_money_addition_f64() { - let money = Money::new(1000.0, Currency::USD()).unwrap(); + let money = Money::new(1000.0, Currency::USD()); let result = money + 500.0; assert_eq!(result, 1500.0); } #[rstest] fn test_money_negation() { - let money = Money::new(100.0, Currency::USD()).unwrap(); + let money = Money::new(100.0, Currency::USD()); let result = -money; assert_eq!(result.as_f64(), -100.0); assert_eq!(result.currency, Currency::USD().clone()); @@ -394,7 +393,7 @@ mod tests { #[rstest] fn test_money_new_usd() { - let money = Money::new(1000.0, Currency::USD()).unwrap(); + let money = Money::new(1000.0, Currency::USD()); assert_eq!(money.currency.code.as_str(), "USD"); assert_eq!(money.currency.precision, 2); assert_eq!(money.to_string(), "1000.00 USD"); @@ -405,7 +404,7 @@ mod tests { #[rstest] fn test_money_new_btc() { - let money = Money::new(10.3, Currency::BTC()).unwrap(); + let money = Money::new(10.3, Currency::BTC()); assert_eq!(money.currency.code.as_str(), "BTC"); assert_eq!(money.currency.precision, 8); assert_eq!(money.to_string(), "10.30000000 BTC"); @@ -414,9 +413,9 @@ mod tests { #[rstest] fn test_money_serialization_deserialization() { - let money = Money::new(123.45, Currency::USD()).unwrap(); - let serialized = serde_json::to_string(&money).unwrap(); - let deserialized: Money = serde_json::from_str(&serialized).unwrap(); + let money = Money::new(123.45, Currency::USD()); + let serialized = serde_json::to_string(&money); + let deserialized: Money = serde_json::from_str(&serialized.unwrap()).unwrap(); assert_eq!(money, deserialized); } @@ -425,9 +424,9 @@ mod tests { #[case("0x00 USD")] // <-- Invalid float #[case("0 US")] // <-- Invalid currency #[case("0 USD USD")] // <-- Too many parts + #[should_panic] fn test_from_str_invalid_input(#[case] input: &str) { - let result = Money::from_str(input); - assert!(result.is_err()); + let _ = Money::from(input); } #[rstest] @@ -440,7 +439,7 @@ mod tests { #[case] expected_currency: Currency, #[case] expected_dec: Decimal, ) { - let money = Money::from_str(input).unwrap(); + let money = Money::from(input); assert_eq!(money.currency, expected_currency); assert_eq!(money.as_decimal(), expected_dec); } diff --git a/nautilus_core/model/src/types/price.rs b/nautilus_core/model/src/types/price.rs index 401b2b254fca..eb269ae761fc 100644 --- a/nautilus_core/model/src/types/price.rs +++ b/nautilus_core/model/src/types/price.rs @@ -21,7 +21,10 @@ use std::{ str::FromStr, }; -use nautilus_core::{correctness::check_in_range_inclusive_f64, parsing::precision_from_str}; +use nautilus_core::{ + correctness::{check_in_range_inclusive_f64, FAILED}, + parsing::precision_from_str, +}; use rust_decimal::Decimal; use serde::{Deserialize, Deserializer, Serialize}; use thousands::Separable; @@ -59,24 +62,23 @@ pub struct Price { } impl Price { - pub fn new(value: f64, precision: u8) -> anyhow::Result { - check_in_range_inclusive_f64(value, PRICE_MIN, PRICE_MAX, "value")?; - check_fixed_precision(precision)?; - - Ok(Self { + pub fn new(value: f64, precision: u8) -> Self { + check_in_range_inclusive_f64(value, PRICE_MIN, PRICE_MAX, "value").expect(FAILED); + check_fixed_precision(precision).expect(FAILED); + Self { raw: f64_to_fixed_i64(value, precision), precision, - }) + } } - pub fn from_raw(raw: i64, precision: u8) -> anyhow::Result { - check_fixed_precision(precision)?; - Ok(Self { raw, precision }) + pub fn from_raw(raw: i64, precision: u8) -> Self { + check_fixed_precision(precision).expect(FAILED); + Self { raw, precision } } #[must_use] pub fn max(precision: u8) -> Self { - check_fixed_precision(precision).unwrap(); + check_fixed_precision(precision).expect(FAILED); Self { raw: (PRICE_MAX * FIXED_SCALAR) as i64, precision, @@ -85,7 +87,7 @@ impl Price { #[must_use] pub fn min(precision: u8) -> Self { - check_fixed_precision(precision).unwrap(); + check_fixed_precision(precision).expect(FAILED); Self { raw: (PRICE_MIN * FIXED_SCALAR) as i64, precision, @@ -94,7 +96,7 @@ impl Price { #[must_use] pub fn zero(precision: u8) -> Self { - check_fixed_precision(precision).unwrap(); + check_fixed_precision(precision).expect(FAILED); Self { raw: 0, precision } } @@ -135,14 +137,13 @@ impl FromStr for Price { .parse::() .map_err(|err| format!("Error parsing `input` string '{input}' as f64: {err}"))?; - Self::new(float_from_input, precision_from_str(input)) - .map_err(|e: anyhow::Error| e.to_string()) + Ok(Self::new(float_from_input, precision_from_str(input))) } } impl From<&str> for Price { fn from(input: &str) -> Self { - Self::from_str(input).unwrap() + Self::from_str(input).expect(FAILED) } } @@ -360,14 +361,14 @@ mod tests { #[should_panic(expected = "Condition failed: `precision` was greater than the maximum ")] fn test_invalid_precision_new() { // Precision out of range for fixed - let _ = Price::new(1.0, 10).unwrap(); + let _ = Price::new(1.0, 10); } #[rstest] #[should_panic(expected = "Condition failed: `precision` was greater than the maximum ")] fn test_invalid_precision_from_raw() { // Precision out of range for fixed - let _ = Price::from_raw(1, 10).unwrap(); + let _ = Price::from_raw(1, 10); } #[rstest] @@ -393,7 +394,7 @@ mod tests { #[rstest] fn test_new() { - let price = Price::new(0.00812, 8).unwrap(); + let price = Price::new(0.00812, 8); assert_eq!(price, price); assert_eq!(price.raw, 8_120_000); assert_eq!(price.precision, 8); @@ -411,7 +412,7 @@ mod tests { #[rstest] fn test_with_maximum_value() { - let price = Price::new(PRICE_MAX, 9).unwrap(); + let price = Price::new(PRICE_MAX, 9); assert_eq!(price.raw, 9_223_372_036_000_000_000); assert_eq!(price.as_decimal(), dec!(9223372036)); assert_eq!(price.to_string(), "9223372036.000000000"); @@ -419,7 +420,7 @@ mod tests { #[rstest] fn test_with_minimum_positive_value() { - let price = Price::new(0.000_000_001, 9).unwrap(); + let price = Price::new(0.000_000_001, 9); assert_eq!(price.raw, 1); assert_eq!(price.as_decimal(), dec!(0.000000001)); assert_eq!(price.to_string(), "0.000000001"); @@ -427,7 +428,7 @@ mod tests { #[rstest] fn test_with_minimum_value() { - let price = Price::new(PRICE_MIN, 9).unwrap(); + let price = Price::new(PRICE_MIN, 9); assert_eq!(price.raw, -9_223_372_036_000_000_000); assert_eq!(price.as_decimal(), dec!(-9223372036)); assert_eq!(price.to_string(), "-9223372036.000000000"); @@ -453,7 +454,7 @@ mod tests { #[rstest] fn test_undefined() { - let price = Price::from_raw(PRICE_UNDEF, 0).unwrap(); + let price = Price::from_raw(PRICE_UNDEF, 0); assert_eq!(price.raw, PRICE_UNDEF); assert!(price.is_undefined()); } @@ -468,7 +469,7 @@ mod tests { #[rstest] fn test_is_zero() { - let price = Price::new(0.0, 8).unwrap(); + let price = Price::new(0.0, 8); assert_eq!(price, price); assert_eq!(price.raw, 0); assert_eq!(price.precision, 8); @@ -479,14 +480,14 @@ mod tests { #[rstest] fn test_precision() { - let price = Price::new(1.001, 2).unwrap(); + let price = Price::new(1.001, 2); assert_eq!(price.raw, 1_000_000_000); assert_eq!(price.to_string(), "1.00"); } #[rstest] fn test_new_from_str() { - let price = Price::from_str("0.00812000").unwrap(); + let price: Price = "0.00812000".into(); assert_eq!(price, price); assert_eq!(price.raw, 8_120_000); assert_eq!(price.precision, 8); @@ -497,8 +498,8 @@ mod tests { #[rstest] fn test_from_str_valid_input() { let input = "10.5"; - let expected_price = Price::new(10.5, precision_from_str(input)).unwrap(); - let result = Price::from_str(input).unwrap(); + let expected_price = Price::new(10.5, precision_from_str(input)); + let result = Price::from(input); assert_eq!(result, expected_price); } @@ -526,52 +527,52 @@ mod tests { #[rstest] fn test_add() { - let price1 = Price::new(1.000, 3).unwrap(); - let price2 = Price::new(1.011, 3).unwrap(); + let price1 = Price::new(1.000, 3); + let price2 = Price::new(1.011, 3); let price3 = price1 + price2; assert_eq!(price3.raw, 2_011_000_000); } #[rstest] fn test_sub() { - let price1 = Price::new(1.011, 3).unwrap(); - let price2 = Price::new(1.000, 3).unwrap(); + let price1 = Price::new(1.011, 3); + let price2 = Price::new(1.000, 3); let price3 = price1 - price2; assert_eq!(price3.raw, 11_000_000); } #[rstest] fn test_add_assign() { - let mut price = Price::new(1.000, 3).unwrap(); - price += Price::new(1.011, 3).unwrap(); + let mut price = Price::new(1.000, 3); + price += Price::new(1.011, 3); assert_eq!(price.raw, 2_011_000_000); } #[rstest] fn test_sub_assign() { - let mut price = Price::new(1.000, 3).unwrap(); - price -= Price::new(0.011, 3).unwrap(); + let mut price = Price::new(1.000, 3); + price -= Price::new(0.011, 3); assert_eq!(price.raw, 989_000_000); } #[rstest] fn test_mul() { - let price1 = Price::new(1.000, 3).unwrap(); - let price2 = Price::new(1.011, 3).unwrap(); + let price1 = Price::new(1.000, 3); + let price2 = Price::new(1.011, 3); let result = price1 * price2.into(); assert!(approx_eq!(f64, result, 1.011, epsilon = 0.000_001)); } #[rstest] fn test_debug() { - let price = Price::from_str("44.12").unwrap(); + let price = Price::from("44.12"); let result = format!("{price:?}"); assert_eq!(result, "Price(44.12)"); } #[rstest] fn test_display() { - let price = Price::from_str("44.12").unwrap(); + let price = Price::from("44.12"); let result = format!("{price}"); assert_eq!(result, "44.12"); } diff --git a/nautilus_core/model/src/types/quantity.rs b/nautilus_core/model/src/types/quantity.rs index 14ac91faf006..c6d7c8d7be46 100644 --- a/nautilus_core/model/src/types/quantity.rs +++ b/nautilus_core/model/src/types/quantity.rs @@ -21,7 +21,10 @@ use std::{ str::FromStr, }; -use nautilus_core::{correctness::check_in_range_inclusive_f64, parsing::precision_from_str}; +use nautilus_core::{ + correctness::{check_in_range_inclusive_f64, FAILED}, + parsing::precision_from_str, +}; use rust_decimal::Decimal; use serde::{Deserialize, Deserializer, Serialize}; use thousands::Separable; @@ -51,25 +54,24 @@ pub struct Quantity { impl Quantity { /// Creates a new [`Quantity`] instance. - pub fn new(value: f64, precision: u8) -> anyhow::Result { - check_in_range_inclusive_f64(value, QUANTITY_MIN, QUANTITY_MAX, "value")?; - check_fixed_precision(precision)?; - - Ok(Self { + pub fn new(value: f64, precision: u8) -> Self { + check_in_range_inclusive_f64(value, QUANTITY_MIN, QUANTITY_MAX, "value").expect(FAILED); + check_fixed_precision(precision).expect(FAILED); + Self { raw: f64_to_fixed_u64(value, precision), precision, - }) + } } - pub fn from_raw(raw: u64, precision: u8) -> anyhow::Result { - check_fixed_precision(precision)?; - Ok(Self { raw, precision }) + pub fn from_raw(raw: u64, precision: u8) -> Self { + check_fixed_precision(precision).expect(FAILED); + Self { raw, precision } } #[must_use] pub fn zero(precision: u8) -> Self { - check_fixed_precision(precision).unwrap(); - Self::new(0.0, precision).unwrap() + check_fixed_precision(precision).expect(FAILED); + Self::new(0.0, precision) } #[must_use] @@ -117,29 +119,9 @@ impl From<&Quantity> for f64 { } } -impl FromStr for Quantity { - type Err = String; - - fn from_str(input: &str) -> Result { - let float_from_input = input - .replace('_', "") - .parse::() - .map_err(|e| format!("Error parsing `input` string '{input}' as f64: {e}"))?; - - Self::new(float_from_input, precision_from_str(input)) - .map_err(|e: anyhow::Error| e.to_string()) - } -} - -impl From<&str> for Quantity { - fn from(input: &str) -> Self { - Self::from_str(input).unwrap() - } -} - impl From for Quantity { fn from(input: i64) -> Self { - Self::new(input as f64, 0).unwrap() + Self::new(input as f64, 0) } } @@ -283,6 +265,25 @@ impl From<&Quantity> for u64 { } } +impl FromStr for Quantity { + type Err = String; + + fn from_str(input: &str) -> Result { + let float_from_input = input + .replace('_', "") + .parse::() + .map_err(|e| format!("Error parsing `input` string '{input}' as f64: {e}"))?; + + Ok(Self::new(float_from_input, precision_from_str(input))) + } +} + +impl From<&str> for Quantity { + fn from(input: &str) -> Self { + Self::from_str(input).expect("Valid string input for `Quantity`") + } +} + impl> AddAssign for Quantity { fn add_assign(&mut self, other: T) { self.raw = self @@ -350,7 +351,7 @@ impl<'de> Deserialize<'de> for Quantity { pub fn check_quantity_positive(value: Quantity) -> anyhow::Result<()> { if !value.is_positive() { - anyhow::bail!("Condition failed: invalid `Quantity`, should be positive and was {value}") + anyhow::bail!("{FAILED}: invalid `Quantity`, should be positive and was {value}") } Ok(()) } @@ -371,7 +372,7 @@ mod tests { #[rstest] #[should_panic(expected = "Condition failed: invalid `Quantity`, should be positive and was 0")] fn test_check_quantity_positive() { - let qty = Quantity::new(0.0, 0).unwrap(); + let qty = Quantity::new(0.0, 0); check_quantity_positive(qty).unwrap(); } @@ -379,14 +380,14 @@ mod tests { #[should_panic(expected = "Condition failed: `precision` was greater than the maximum ")] fn test_invalid_precision_new() { // Precision out of range for fixed - let _ = Quantity::new(1.0, 10).unwrap(); + let _ = Quantity::new(1.0, 10); } #[rstest] #[should_panic(expected = "Condition failed: `precision` was greater than the maximum ")] fn test_invalid_precision_from_raw() { // Precision out of range for fixed - let _ = Quantity::from_raw(1, 10).unwrap(); + let _ = Quantity::from_raw(1, 10); } #[rstest] @@ -398,7 +399,7 @@ mod tests { #[rstest] fn test_new() { - let qty = Quantity::new(0.00812, 8).unwrap(); + let qty = Quantity::new(0.00812, 8); assert_eq!(qty, qty); assert_eq!(qty.raw, 8_120_000); assert_eq!(qty.precision, 8); @@ -412,7 +413,7 @@ mod tests { #[rstest] fn test_undefined() { - let qty = Quantity::from_raw(QUANTITY_UNDEF, 0).unwrap(); + let qty = Quantity::from_raw(QUANTITY_UNDEF, 0); assert_eq!(qty.raw, QUANTITY_UNDEF); assert!(qty.is_undefined()); } @@ -436,7 +437,7 @@ mod tests { #[rstest] fn test_with_maximum_value() { - let qty = Quantity::new(QUANTITY_MAX, 8).unwrap(); + let qty = Quantity::new(QUANTITY_MAX, 8); assert_eq!(qty.raw, 18_446_744_073_000_000_000); assert_eq!(qty.as_decimal(), dec!(18_446_744_073)); assert_eq!(qty.to_string(), "18446744073.00000000"); @@ -445,7 +446,7 @@ mod tests { #[rstest] fn test_with_minimum_positive_value() { - let qty = Quantity::new(0.000_000_001, 9).unwrap(); + let qty = Quantity::new(0.000_000_001, 9); assert_eq!(qty.raw, 1); assert_eq!(qty.as_decimal(), dec!(0.000000001)); assert_eq!(qty.to_string(), "0.000000001"); @@ -453,7 +454,7 @@ mod tests { #[rstest] fn test_with_minimum_value() { - let qty = Quantity::new(QUANTITY_MIN, 9).unwrap(); + let qty = Quantity::new(QUANTITY_MIN, 9); assert_eq!(qty.raw, 0); assert_eq!(qty.as_decimal(), dec!(0)); assert_eq!(qty.to_string(), "0.000000000"); @@ -473,14 +474,14 @@ mod tests { #[rstest] fn test_precision() { - let qty = Quantity::new(1.001, 2).unwrap(); + let qty = Quantity::new(1.001, 2); assert_eq!(qty.raw, 1_000_000_000); assert_eq!(qty.to_string(), "1.00"); } #[rstest] fn test_new_from_str() { - let qty = Quantity::from_str("0.00812000").unwrap(); + let qty = Quantity::new(0.00812000, 8); assert_eq!(qty, qty); assert_eq!(qty.raw, 8_120_000); assert_eq!(qty.precision, 8); @@ -493,80 +494,71 @@ mod tests { #[case("1.1", 1)] #[case("1.123456789", 9)] fn test_from_str_valid_input(#[case] input: &str, #[case] expected_prec: u8) { - let qty = Quantity::from_str(input).unwrap(); + let qty = Quantity::from(input); assert_eq!(qty.precision, expected_prec); assert_eq!(qty.as_decimal(), Decimal::from_str(input).unwrap()); } #[rstest] + #[should_panic] fn test_from_str_invalid_input() { let input = "invalid"; - let result = Quantity::from_str(input); - assert!(result.is_err()); + Quantity::new(f64::from_str(input).unwrap(), 8); } #[rstest] fn test_add() { - let quantity1 = Quantity::new(1.0, 0).unwrap(); - let quantity2 = Quantity::new(2.0, 0).unwrap(); + let quantity1 = Quantity::new(1.0, 0); + let quantity2 = Quantity::new(2.0, 0); let quantity3 = quantity1 + quantity2; assert_eq!(quantity3.raw, 3_000_000_000); } #[rstest] fn test_sub() { - let quantity1 = Quantity::new(3.0, 0).unwrap(); - let quantity2 = Quantity::new(2.0, 0).unwrap(); + let quantity1 = Quantity::new(3.0, 0); + let quantity2 = Quantity::new(2.0, 0); let quantity3 = quantity1 - quantity2; assert_eq!(quantity3.raw, 1_000_000_000); } #[rstest] fn test_add_assign() { - let mut quantity1 = Quantity::new(1.0, 0).unwrap(); - let quantity2 = Quantity::new(2.0, 0).unwrap(); + let mut quantity1 = Quantity::new(1.0, 0); + let quantity2 = Quantity::new(2.0, 0); quantity1 += quantity2; assert_eq!(quantity1.raw, 3_000_000_000); } #[rstest] fn test_sub_assign() { - let mut quantity1 = Quantity::new(3.0, 0).unwrap(); - let quantity2 = Quantity::new(2.0, 0).unwrap(); + let mut quantity1 = Quantity::new(3.0, 0); + let quantity2 = Quantity::new(2.0, 0); quantity1 -= quantity2; assert_eq!(quantity1.raw, 1_000_000_000); } #[rstest] fn test_mul() { - let quantity1 = Quantity::new(2.0, 1).unwrap(); - let quantity2 = Quantity::new(2.0, 1).unwrap(); + let quantity1 = Quantity::new(2.0, 1); + let quantity2 = Quantity::new(2.0, 1); let quantity3 = quantity1 * quantity2; assert_eq!(quantity3.raw, 4_000_000_000); } #[rstest] fn test_equality() { - assert_eq!( - Quantity::new(1.0, 1).unwrap(), - Quantity::new(1.0, 1).unwrap() - ); - assert_eq!( - Quantity::new(1.0, 1).unwrap(), - Quantity::new(1.0, 2).unwrap() - ); - assert_ne!( - Quantity::new(1.1, 1).unwrap(), - Quantity::new(1.0, 1).unwrap() - ); - assert!(Quantity::new(1.0, 1).unwrap() <= Quantity::new(1.0, 2).unwrap()); - assert!(Quantity::new(1.1, 1).unwrap() > Quantity::new(1.0, 1).unwrap()); - assert!(Quantity::new(1.0, 1).unwrap() >= Quantity::new(1.0, 1).unwrap()); - assert!(Quantity::new(1.0, 1).unwrap() >= Quantity::new(1.0, 2).unwrap()); - assert!(Quantity::new(1.0, 1).unwrap() >= Quantity::new(1.0, 2).unwrap()); - assert!(Quantity::new(0.9, 1).unwrap() < Quantity::new(1.0, 1).unwrap()); - assert!(Quantity::new(0.9, 1).unwrap() <= Quantity::new(1.0, 2).unwrap()); - assert!(Quantity::new(0.9, 1).unwrap() <= Quantity::new(1.0, 1).unwrap()); + assert_eq!(Quantity::new(1.0, 1), Quantity::new(1.0, 1)); + assert_eq!(Quantity::new(1.0, 1), Quantity::new(1.0, 2)); + assert_ne!(Quantity::new(1.1, 1), Quantity::new(1.0, 1)); + assert!(Quantity::new(1.0, 1) <= Quantity::new(1.0, 2)); + assert!(Quantity::new(1.1, 1) > Quantity::new(1.0, 1)); + assert!(Quantity::new(1.0, 1) >= Quantity::new(1.0, 1)); + assert!(Quantity::new(1.0, 1) >= Quantity::new(1.0, 2)); + assert!(Quantity::new(1.0, 1) >= Quantity::new(1.0, 2)); + assert!(Quantity::new(0.9, 1) < Quantity::new(1.0, 1)); + assert!(Quantity::new(0.9, 1) <= Quantity::new(1.0, 2)); + assert!(Quantity::new(0.9, 1) <= Quantity::new(1.0, 1)); } #[rstest] diff --git a/nautilus_core/model/src/types/stubs.rs b/nautilus_core/model/src/types/stubs.rs index c81acd81e64a..2c46591f7b83 100644 --- a/nautilus_core/model/src/types/stubs.rs +++ b/nautilus_core/model/src/types/stubs.rs @@ -27,7 +27,7 @@ pub fn account_balance_test() -> AccountBalance { let total = Money::from("1525000 USD"); let locked = Money::from("25000 USD"); let free = Money::from("1500000 USD"); - AccountBalance::new(total, locked, free).unwrap() + AccountBalance::new(total, locked, free) } #[fixture] @@ -35,5 +35,5 @@ pub fn margin_balance_test() -> MarginBalance { let initial = Money::from("5000 USD"); let maintenance = Money::from("20000 USD"); let instrument = instrument_id_btc_usdt(); - MarginBalance::new(initial, maintenance, instrument).unwrap() + MarginBalance::new(initial, maintenance, instrument) } diff --git a/nautilus_core/persistence/src/arrow/bar.rs b/nautilus_core/persistence/src/arrow/bar.rs index 8c106ed01f7b..de8dca47d79d 100644 --- a/nautilus_core/persistence/src/arrow/bar.rs +++ b/nautilus_core/persistence/src/arrow/bar.rs @@ -137,11 +137,11 @@ impl DecodeFromRecordBatch for Bar { let result: Result, EncodingError> = (0..record_batch.num_rows()) .map(|i| { - let open = Price::from_raw(open_values.value(i), price_precision).unwrap(); - let high = Price::from_raw(high_values.value(i), price_precision).unwrap(); - let low = Price::from_raw(low_values.value(i), price_precision).unwrap(); - let close = Price::from_raw(close_values.value(i), price_precision).unwrap(); - let volume = Quantity::from_raw(volume_values.value(i), size_precision).unwrap(); + let open = Price::from_raw(open_values.value(i), price_precision); + let high = Price::from_raw(high_values.value(i), price_precision); + let low = Price::from_raw(low_values.value(i), price_precision); + let close = Price::from_raw(close_values.value(i), price_precision); + let volume = Quantity::from_raw(volume_values.value(i), size_precision); let ts_event = ts_event_values.value(i).into(); let ts_init = ts_init_values.value(i).into(); diff --git a/nautilus_core/persistence/src/arrow/delta.rs b/nautilus_core/persistence/src/arrow/delta.rs index e5cf86d9c2ef..d89244ad7684 100644 --- a/nautilus_core/persistence/src/arrow/delta.rs +++ b/nautilus_core/persistence/src/arrow/delta.rs @@ -167,8 +167,8 @@ impl DecodeFromRecordBatch for OrderBookDelta { format!("Invalid enum value, was {side_value}"), ) })?; - let price = Price::from_raw(price_values.value(i), price_precision).unwrap(); - let size = Quantity::from_raw(size_values.value(i), size_precision).unwrap(); + let price = Price::from_raw(price_values.value(i), price_precision); + let size = Quantity::from_raw(size_values.value(i), size_precision); let order_id = order_id_values.value(i); let flags = flags_values.value(i); let sequence = sequence_values.value(i); diff --git a/nautilus_core/persistence/src/arrow/depth.rs b/nautilus_core/persistence/src/arrow/depth.rs index 6542b5096a3b..9723e9f00fd1 100644 --- a/nautilus_core/persistence/src/arrow/depth.rs +++ b/nautilus_core/persistence/src/arrow/depth.rs @@ -376,15 +376,15 @@ impl DecodeFromRecordBatch for OrderBookDepth10 { for j in 0..DEPTH10_LEN { bids[j] = BookOrder::new( OrderSide::Buy, - Price::from_raw(bid_prices[j].value(i), price_precision).unwrap(), - Quantity::from_raw(bid_sizes[j].value(i), size_precision).unwrap(), + Price::from_raw(bid_prices[j].value(i), price_precision), + Quantity::from_raw(bid_sizes[j].value(i), size_precision), 0, // Order ID always zero ); asks[j] = BookOrder::new( OrderSide::Sell, - Price::from_raw(ask_prices[j].value(i), price_precision).unwrap(), - Quantity::from_raw(ask_sizes[j].value(i), size_precision).unwrap(), + Price::from_raw(ask_prices[j].value(i), price_precision), + Quantity::from_raw(ask_sizes[j].value(i), size_precision), 0, // Order ID always zero ); bid_count_arr[j] = bid_counts[j].value(i); diff --git a/nautilus_core/persistence/src/arrow/quote.rs b/nautilus_core/persistence/src/arrow/quote.rs index 93f6e2f97092..e8a8637b56ae 100644 --- a/nautilus_core/persistence/src/arrow/quote.rs +++ b/nautilus_core/persistence/src/arrow/quote.rs @@ -134,14 +134,10 @@ impl DecodeFromRecordBatch for QuoteTick { let result: Result, EncodingError> = (0..record_batch.num_rows()) .map(|i| { - let bid_price = - Price::from_raw(bid_price_values.value(i), price_precision).unwrap(); - let ask_price = - Price::from_raw(ask_price_values.value(i), price_precision).unwrap(); - let bid_size = - Quantity::from_raw(bid_size_values.value(i), size_precision).unwrap(); - let ask_size = - Quantity::from_raw(ask_size_values.value(i), size_precision).unwrap(); + let bid_price = Price::from_raw(bid_price_values.value(i), price_precision); + let ask_price = Price::from_raw(ask_price_values.value(i), price_precision); + let bid_size = Quantity::from_raw(bid_size_values.value(i), size_precision); + let ask_size = Quantity::from_raw(ask_size_values.value(i), size_precision); let ts_event = ts_event_values.value(i).into(); let ts_init = ts_init_values.value(i).into(); diff --git a/nautilus_core/persistence/src/arrow/trade.rs b/nautilus_core/persistence/src/arrow/trade.rs index 14a9704f13d4..d6a02488abe7 100644 --- a/nautilus_core/persistence/src/arrow/trade.rs +++ b/nautilus_core/persistence/src/arrow/trade.rs @@ -136,8 +136,8 @@ impl DecodeFromRecordBatch for TradeTick { let result: Result, EncodingError> = (0..record_batch.num_rows()) .map(|i| { - let price = Price::from_raw(price_values.value(i), price_precision).unwrap(); - let size = Quantity::from_raw(size_values.value(i), size_precision).unwrap(); + let price = Price::from_raw(price_values.value(i), price_precision); + let size = Quantity::from_raw(size_values.value(i), size_precision); let aggressor_side_value = aggressor_side_values.value(i); let aggressor_side = AggressorSide::from_repr(aggressor_side_value as usize) .ok_or_else(|| { @@ -232,7 +232,7 @@ mod tests { price: Price::from("100.10"), size: Quantity::from(1000), aggressor_side: AggressorSide::Buyer, - trade_id: TradeId::new("1").unwrap(), + trade_id: TradeId::new("1"), ts_event: 1.into(), ts_init: 3.into(), }; @@ -242,7 +242,7 @@ mod tests { price: Price::from("100.50"), size: Quantity::from(500), aggressor_side: AggressorSide::Seller, - trade_id: TradeId::new("2").unwrap(), + trade_id: TradeId::new("2"), ts_event: 2.into(), ts_init: 4.into(), }; diff --git a/tests/unit_tests/model/objects/test_money_pyo3.py b/tests/unit_tests/model/objects/test_money_pyo3.py index 69a0278bbe63..6063526fbb12 100644 --- a/tests/unit_tests/model/objects/test_money_pyo3.py +++ b/tests/unit_tests/model/objects/test_money_pyo3.py @@ -31,7 +31,7 @@ class TestMoney: def test_instantiate_with_nan_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Money(math.nan, currency=USD) def test_instantiate_with_none_value_raises_type_error(self) -> None: @@ -46,12 +46,12 @@ def test_instantiate_with_none_currency_raises_type_error(self) -> None: def test_instantiate_with_value_exceeding_positive_limit_raises_value_error(self) -> None: # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Money(9_223_372_036 + 1, currency=USD) def test_instantiate_with_value_exceeding_negative_limit_raises_value_error(self) -> None: # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Money(-9_223_372_036 - 1, currency=USD) @pytest.mark.parametrize( diff --git a/tests/unit_tests/model/objects/test_price_pyo3.py b/tests/unit_tests/model/objects/test_price_pyo3.py index d2ca72095dda..b1ccf1afe15e 100644 --- a/tests/unit_tests/model/objects/test_price_pyo3.py +++ b/tests/unit_tests/model/objects/test_price_pyo3.py @@ -25,7 +25,7 @@ class TestPrice: def test_instantiate_with_nan_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Price(math.nan, precision=0) def test_instantiate_with_none_value_raises_type_error(self): @@ -40,17 +40,17 @@ def test_instantiate_with_negative_precision_raises_overflow_error(self): def test_instantiate_with_precision_over_maximum_raises_overflow_error(self): # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Price(1.0, precision=10) def test_instantiate_with_value_exceeding_positive_limit_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Price(9_223_372_036 + 1, precision=0) def test_instantiate_with_value_exceeding_negative_limit_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Price(-9_223_372_036 - 1, precision=0) def test_instantiate_base_decimal_from_int(self): diff --git a/tests/unit_tests/model/objects/test_quantity_pyo3.py b/tests/unit_tests/model/objects/test_quantity_pyo3.py index 4ea689e1d5ae..a664055d3f85 100644 --- a/tests/unit_tests/model/objects/test_quantity_pyo3.py +++ b/tests/unit_tests/model/objects/test_quantity_pyo3.py @@ -25,7 +25,7 @@ class TestQuantity: def test_instantiate_with_nan_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Quantity(math.nan, precision=0) def test_instantiate_with_none_value_raises_type_error(self): @@ -43,12 +43,12 @@ def test_instantiate_with_negative_precision_raises_overflow_error(self): def test_instantiate_with_precision_over_maximum_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Quantity(1.0, precision=10) def test_instantiate_with_value_exceeding_limit_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Quantity(18_446_744_073 + 1, precision=0) def test_instantiate_base_decimal_from_int(self): diff --git a/tests/unit_tests/model/test_currency_pyo3.py b/tests/unit_tests/model/test_currency_pyo3.py index 7ab128f18b28..0f2e0695ae30 100644 --- a/tests/unit_tests/model/test_currency_pyo3.py +++ b/tests/unit_tests/model/test_currency_pyo3.py @@ -41,7 +41,7 @@ def test_currency_with_negative_precision_raises_overflow_error(self): def test_currency_with_precision_over_maximum_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(ValueError): + with pytest.raises(BaseException): Currency( code="AUD", precision=10, diff --git a/tests/unit_tests/model/test_identifiers.py b/tests/unit_tests/model/test_identifiers.py index d68646d18f0d..dffe4732de8f 100644 --- a/tests/unit_tests/model/test_identifiers.py +++ b/tests/unit_tests/model/test_identifiers.py @@ -208,11 +208,11 @@ def test_instrument_id_from_str() -> None: ], [ ".USDT", - "Error parsing `InstrumentId` from '.USDT': Condition failed: invalid string for 'value', was empty", + "invalid string for 'value', was empty", # TODO: Improve error message ], [ "BTC.", - "Error parsing `InstrumentId` from 'BTC.': Condition failed: invalid string for 'value', was empty", + "invalid string for 'value', was empty", # TODO: Improve error message ], ], ) diff --git a/tests/unit_tests/model/test_identifiers_pyo3.py b/tests/unit_tests/model/test_identifiers_pyo3.py index e38b61da406e..0c551fa8816b 100644 --- a/tests/unit_tests/model/test_identifiers_pyo3.py +++ b/tests/unit_tests/model/test_identifiers_pyo3.py @@ -190,17 +190,17 @@ def test_instrument_id_from_str() -> None: ], [ ".USDT", - "Error parsing `InstrumentId` from '.USDT': Condition failed: invalid string for 'value', was empty", + "invalid string for 'value', was empty", # TODO: Improve error message ], [ "BTC.", - "Error parsing `InstrumentId` from 'BTC.': Condition failed: invalid string for 'value', was empty", + "invalid string for 'value', was empty", # TODO: Improve error message ], ], ) def test_instrument_id_from_str_when_invalid(input: str, expected_err: str) -> None: # Arrange, Act - with pytest.raises(ValueError) as exc_info: + with pytest.raises(BaseException) as exc_info: InstrumentId.from_str(input) # Assert From 14f5a09b59dc519b811a08e8f6ef20ea0b52982e Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Wed, 14 Aug 2024 22:39:19 +1000 Subject: [PATCH 37/72] Update release notes --- RELEASES.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 66fb8747fe33..023a78d9ddeb 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,9 +4,10 @@ Released on TBD (UTC). ### Enhancements - Added `LiveExecEngineConfig.generate_missing_orders` reconciliation config option to align internal and external position states -- Improve `BinanceExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) -- Improve `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) -- Improve `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions +- Improved `BinanceExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) +- Improved `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) +- Improved `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions +- Improved `@customdataclass` decorator constructor to allow more positional arguments, thanks @faysou - Upgraded `datafusion` crate to v41.0.0 ### Breaking Changes @@ -23,7 +24,7 @@ Released on TBD (UTC). Released on 9th August 2024 (UTC). ### Enhancements -- Added `@customdata` decorator to reduce need for boiler plate implementing custom data types (#1828), thanks @faysou +- Added `@customdataclass` decorator to reduce need for boiler plate implementing custom data types (#1828), thanks @faysou - Added timeout for HTTP client in Rust (#1835), thanks @davidsblom - Added catalog conversion function of streamed data to backtest data (#1834), thanks @faysou - Upgraded Cython to v3.0.11 From 9a47e154940f2e026550cf916b2569bd0c091811 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Thu, 15 Aug 2024 20:30:38 +1000 Subject: [PATCH 38/72] Refine some condition checks --- nautilus_core/adapters/src/databento/symbology.rs | 4 ++-- nautilus_core/common/src/cache/mod.rs | 8 ++++---- nautilus_core/common/src/xrate.rs | 4 ++-- nautilus_core/data/src/aggregation.rs | 10 ++-------- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/nautilus_core/adapters/src/databento/symbology.rs b/nautilus_core/adapters/src/databento/symbology.rs index c8708c40534f..a1c08da165e5 100644 --- a/nautilus_core/adapters/src/databento/symbology.rs +++ b/nautilus_core/adapters/src/databento/symbology.rs @@ -146,10 +146,10 @@ mod tests { } #[rstest] + #[should_panic] fn test_check_consistent_symbology_when_empty_symbols() { let symbols: Vec<&str> = vec![]; - let result = std::panic::catch_unwind(|| check_consistent_symbology(&symbols)); - assert!(result.is_err()); + let _ = check_consistent_symbology(&symbols); } #[rstest] diff --git a/nautilus_core/common/src/cache/mod.rs b/nautilus_core/common/src/cache/mod.rs index dba05bb5a213..4f8e63cdc377 100644 --- a/nautilus_core/common/src/cache/mod.rs +++ b/nautilus_core/common/src/cache/mod.rs @@ -1306,28 +1306,28 @@ impl Cache { stringify!(client_order_id), stringify!(orders), ) - .unwrap(); + .expect(FAILED); check_key_not_in_map( &client_order_id, &self.orders, stringify!(client_order_id), stringify!(orders), ) - .unwrap(); + .expect(FAILED); check_key_not_in_map( &client_order_id, &self.orders, stringify!(client_order_id), stringify!(orders), ) - .unwrap(); + .expect(FAILED); check_key_not_in_map( &client_order_id, &self.orders, stringify!(client_order_id), stringify!(orders), ) - .unwrap(); + .expect(FAILED); }; log::debug!("Adding {:?}", order); diff --git a/nautilus_core/common/src/xrate.rs b/nautilus_core/common/src/xrate.rs index 910c1b34f5df..eeccb0eec242 100644 --- a/nautilus_core/common/src/xrate.rs +++ b/nautilus_core/common/src/xrate.rs @@ -42,8 +42,8 @@ pub fn get_exchange_rate( quotes_bid: HashMap, quotes_ask: HashMap, ) -> anyhow::Result { - check_map_not_empty("es_bid, stringify!(quotes_bid)).unwrap(); - check_map_not_empty("es_ask, stringify!(quotes_ask)).unwrap(); + check_map_not_empty("es_bid, stringify!(quotes_bid)).expect(FAILED); + check_map_not_empty("es_ask, stringify!(quotes_ask)).expect(FAILED); check_equal_usize( quotes_bid.len(), quotes_ask.len(), diff --git a/nautilus_core/data/src/aggregation.rs b/nautilus_core/data/src/aggregation.rs index 65c0ebe727fc..9c5aefbb9e7e 100644 --- a/nautilus_core/data/src/aggregation.rs +++ b/nautilus_core/data/src/aggregation.rs @@ -583,8 +583,6 @@ where //////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use std::panic::AssertUnwindSafe; - use nautilus_model::{ data::bar::{BarSpecification, BarType}, enums::{AggregationSource, BarAggregation, PriceType}, @@ -764,6 +762,7 @@ mod tests { } #[rstest] + #[should_panic] fn test_bar_builder_build_when_no_updates_panics(equity_aapl: Equity) { let instrument = InstrumentAny::Equity(equity_aapl); let bar_type = BarType::new( @@ -772,12 +771,7 @@ mod tests { AggregationSource::Internal, ); let mut builder = BarBuilder::new(&instrument, bar_type); - - let result = std::panic::catch_unwind(AssertUnwindSafe(|| { - builder.build_now(); - })); - - assert!(result.is_err()); + let _ = builder.build_now(); } #[rstest] From 4cba843beed53967f88383470e67b4b19166ad53 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Thu, 15 Aug 2024 20:32:50 +1000 Subject: [PATCH 39/72] Update release notes --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index 023a78d9ddeb..fce25cf1016a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,6 +8,7 @@ Released on TBD (UTC). - Improved `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improved `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions - Improved `@customdataclass` decorator constructor to allow more positional arguments, thanks @faysou +- Refined error modeling and handling in Rust (#1849), thanks @twitu - Upgraded `datafusion` crate to v41.0.0 ### Breaking Changes From 2be5671b029cb48a7a5924c6983ab0e99becbddc Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Thu, 15 Aug 2024 20:37:15 +1000 Subject: [PATCH 40/72] Update dependencies --- nautilus_core/Cargo.lock | 114 ++++++++++++++++++++++----------------- nautilus_core/Cargo.toml | 6 +-- poetry.lock | 22 ++------ 3 files changed, 71 insertions(+), 71 deletions(-) diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index ede87dcd0084..dfde9051a5c0 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -305,7 +305,7 @@ dependencies = [ "arrow-schema", "chrono", "half", - "indexmap 2.3.0", + "indexmap 2.4.0", "lexical-core", "num", "serde", @@ -733,12 +733,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "5fb8dd288a69fc53a1996d7ecfbf4a20d59065bff137ce7e56bbd620de191189" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -980,9 +981,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -1229,7 +1230,7 @@ dependencies = [ "glob", "half", "hashbrown 0.14.5", - "indexmap 2.3.0", + "indexmap 2.4.0", "itertools 0.12.1", "log", "num_cpus", @@ -1389,7 +1390,7 @@ dependencies = [ "datafusion-expr", "datafusion-physical-expr", "hashbrown 0.14.5", - "indexmap 2.3.0", + "indexmap 2.4.0", "itertools 0.12.1", "log", "paste", @@ -1418,7 +1419,7 @@ dependencies = [ "half", "hashbrown 0.14.5", "hex", - "indexmap 2.3.0", + "indexmap 2.4.0", "itertools 0.12.1", "log", "paste", @@ -1476,7 +1477,7 @@ dependencies = [ "futures", "half", "hashbrown 0.14.5", - "indexmap 2.3.0", + "indexmap 2.4.0", "itertools 0.12.1", "log", "once_cell", @@ -1940,7 +1941,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.3.0", + "indexmap 2.4.0", "slab", "tokio", "tokio-util", @@ -2013,6 +2014,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -2229,9 +2236,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -2270,11 +2277,11 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -2329,9 +2336,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -2587,7 +2594,7 @@ dependencies = [ "criterion", "databento", "fallible-streaming-iterator", - "indexmap 2.3.0", + "indexmap 2.4.0", "itoa", "nautilus-common", "nautilus-core", @@ -2615,7 +2622,7 @@ dependencies = [ "anyhow", "chrono", "criterion", - "indexmap 2.3.0", + "indexmap 2.4.0", "nautilus-common", "nautilus-core", "nautilus-model", @@ -2675,7 +2682,7 @@ dependencies = [ "bytes", "cbindgen", "chrono", - "indexmap 2.3.0", + "indexmap 2.4.0", "itertools 0.12.1", "log", "nautilus-core", @@ -2726,7 +2733,7 @@ dependencies = [ "chrono", "criterion", "derive_builder", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "nautilus-common", "nautilus-core", @@ -2751,7 +2758,7 @@ dependencies = [ "anyhow", "criterion", "derive_builder", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "nautilus-common", "nautilus-core", @@ -2821,7 +2828,7 @@ dependencies = [ "evalexpr", "float-cmp", "iai", - "indexmap 2.3.0", + "indexmap 2.4.0", "nautilus-core", "once_cell", "pyo3", @@ -3304,7 +3311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.3.0", + "indexmap 2.4.0", ] [[package]] @@ -4198,18 +4205,18 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.206" +version = "1.0.207" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" +checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.206" +version = "1.0.207" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" +checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" dependencies = [ "proc-macro2", "quote", @@ -4218,9 +4225,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", "memchr", @@ -4287,6 +4294,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -4470,7 +4483,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink", "hex", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "memchr", "once_cell", @@ -5079,7 +5092,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.3.0", + "indexmap 2.4.0", "toml_datetime", "winnow", ] @@ -5102,15 +5115,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -5417,19 +5430,20 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -5442,9 +5456,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -5454,9 +5468,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5464,9 +5478,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -5477,9 +5491,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-streams" @@ -5496,9 +5510,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/nautilus_core/Cargo.toml b/nautilus_core/Cargo.toml index e2b785fad5a3..31b7b3fc4027 100644 --- a/nautilus_core/Cargo.toml +++ b/nautilus_core/Cargo.toml @@ -32,7 +32,7 @@ bytes = { version = "1.7.1", features = ["serde"] } chrono = "0.4.38" derive_builder = "0.20.0" futures = "0.3.30" -indexmap = { version = "2.3.0", features = ["serde"] } +indexmap = { version = "2.4.0", features = ["serde"] } itertools = "0.12.1" itoa = "1.0.11" once_cell = "1.19.0" @@ -44,8 +44,8 @@ rmp-serde = "1.3.0" rust_decimal = "1.35.0" rust_decimal_macros = "1.35.0" semver = "1.0.23" -serde = { version = "1.0.206", features = ["derive"] } -serde_json = "1.0.124" +serde = { version = "1.0.207", features = ["derive"] } +serde_json = "1.0.125" strum = { version = "0.26.3", features = ["derive"] } thiserror = "1.0.63" thousands = "0.2.0" diff --git a/poetry.lock b/poetry.lock index f178fbbeb7ce..3739363e5aaf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1354,7 +1354,6 @@ files = [ {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, @@ -1549,19 +1548,6 @@ files = [ {file = "pyarrow-17.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:392bc9feabc647338e6c89267635e111d71edad5fcffba204425a7c8d13610d7"}, {file = "pyarrow-17.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:af5ff82a04b2171415f1410cff7ebb79861afc5dae50be73ce06d6e870615204"}, {file = "pyarrow-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:edca18eaca89cd6382dfbcff3dd2d87633433043650c07375d095cd3517561d8"}, - {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c7916bff914ac5d4a8fe25b7a25e432ff921e72f6f2b7547d1e325c1ad9d155"}, - {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f553ca691b9e94b202ff741bdd40f6ccb70cdd5fbf65c187af132f1317de6145"}, - {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0cdb0e627c86c373205a2f94a510ac4376fdc523f8bb36beab2e7f204416163c"}, - {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:d7d192305d9d8bc9082d10f361fc70a73590a4c65cf31c3e6926cd72b76bc35c"}, - {file = "pyarrow-17.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:02dae06ce212d8b3244dd3e7d12d9c4d3046945a5933d28026598e9dbbda1fca"}, - {file = "pyarrow-17.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:13d7a460b412f31e4c0efa1148e1d29bdf18ad1411eb6757d38f8fbdcc8645fb"}, - {file = "pyarrow-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b564a51fbccfab5a04a80453e5ac6c9954a9c5ef2890d1bcf63741909c3f8df"}, - {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32503827abbc5aadedfa235f5ece8c4f8f8b0a3cf01066bc8d29de7539532687"}, - {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a155acc7f154b9ffcc85497509bcd0d43efb80d6f733b0dc3bb14e281f131c8b"}, - {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:dec8d129254d0188a49f8a1fc99e0560dc1b85f60af729f47de4046015f9b0a5"}, - {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:a48ddf5c3c6a6c505904545c25a4ae13646ae1f8ba703c4df4a1bfe4f4006bda"}, - {file = "pyarrow-17.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:42bf93249a083aca230ba7e2786c5f673507fa97bbd9725a1e2754715151a204"}, - {file = "pyarrow-17.0.0.tar.gz", hash = "sha256:4beca9521ed2c0921c1023e68d097d0299b62c362639ea315572a58f3f50fd28"}, ] [package.dependencies] @@ -1879,18 +1865,18 @@ files = [ [[package]] name = "setuptools" -version = "72.1.0" +version = "72.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, - {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, + {file = "setuptools-72.2.0-py3-none-any.whl", hash = "sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4"}, + {file = "setuptools-72.2.0.tar.gz", hash = "sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9"}, ] [package.extras] core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] From 0d36a3e5dafbebdcbc5de48546eca9c6bd70f741 Mon Sep 17 00:00:00 2001 From: faysou Date: Thu, 15 Aug 2024 13:37:06 +0100 Subject: [PATCH 41/72] Small improvement to custom data documentation (#1852) --- docs/concepts/advanced/custom_data.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/concepts/advanced/custom_data.md b/docs/concepts/advanced/custom_data.md index 5a5c0dde434d..93770d7bc8fd 100644 --- a/docs/concepts/advanced/custom_data.md +++ b/docs/concepts/advanced/custom_data.md @@ -257,3 +257,20 @@ GreeksTestData( ts_init=2, ) ``` + +For more convenience, you can use a pyi file with a proper signature so it's usable for code suggestions in an IDE, as the constructor is generated at runtime. +For example in a file named `greeks.pyi` if the custom data class is defined in `greeks.py`, the signature of the constructor would be: + +```python +from nautilus_trader.core.data import Data +from nautilus_trader.model.identifiers import InstrumentId + + +class GreeksData(Data): + def __init__(self, + ts_event: int = 0, + ts_init: int = 0, + instrument_id: InstrumentId = InstrumentId.from_str('ES.GLBX'), + delta: float = 0.) -> GreeksData: + ... +``` From 059e348f978748261c7fbd04b1e9201ab5ce6373 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Fri, 16 Aug 2024 18:10:15 +1000 Subject: [PATCH 42/72] Improve customdataclass doc --- docs/concepts/advanced/custom_data.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/concepts/advanced/custom_data.md b/docs/concepts/advanced/custom_data.md index 93770d7bc8fd..0bd9a727ac7e 100644 --- a/docs/concepts/advanced/custom_data.md +++ b/docs/concepts/advanced/custom_data.md @@ -258,8 +258,14 @@ GreeksTestData( ) ``` -For more convenience, you can use a pyi file with a proper signature so it's usable for code suggestions in an IDE, as the constructor is generated at runtime. -For example in a file named `greeks.pyi` if the custom data class is defined in `greeks.py`, the signature of the constructor would be: +### Custom data type stub + +To enhance development convenience and improve code suggestions in your IDE, you can create a `.pyi` +stub file with the proper constructor signature for your custom data types. This is particularly +useful when the constructor is dynamically generated at runtime, as it allows the IDE to recognize +and provide suggestions for the class's methods and attributes. + +For instance, if you have a custom data class defined in `greeks.py`, you can create a corresponding `greeks.pyi` file with the following constructor signature: ```python from nautilus_trader.core.data import Data @@ -267,10 +273,12 @@ from nautilus_trader.model.identifiers import InstrumentId class GreeksData(Data): - def __init__(self, - ts_event: int = 0, - ts_init: int = 0, - instrument_id: InstrumentId = InstrumentId.from_str('ES.GLBX'), - delta: float = 0.) -> GreeksData: + def __init__( + self, + ts_event: int = 0, + ts_init: int = 0, + instrument_id: InstrumentId = InstrumentId.from_str("ES.GLBX"), + delta: float = 0.0, + ) -> GreeksData: ... ``` From cbed4fb8a8c9f7adc4aebc1b338494525d34f964 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Fri, 16 Aug 2024 18:27:24 +1000 Subject: [PATCH 43/72] Update pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 12afb3e256f3..d9f30b884e45 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -83,7 +83,7 @@ repos: exclude: "docs/_pygments/monokai.py" - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.7 + rev: v0.6.0 hooks: - id: ruff args: ["--fix"] From 06e1be48715e079472da2d6715468b7b396a3d1f Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Fri, 16 Aug 2024 18:27:37 +1000 Subject: [PATCH 44/72] Update core dependenices --- nautilus_core/Cargo.lock | 24 ++++++++++++------------ nautilus_core/Cargo.toml | 2 +- nautilus_core/cli/Cargo.toml | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index dfde9051a5c0..f95ffab195ad 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -733,9 +733,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.11" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb8dd288a69fc53a1996d7ecfbf4a20d59065bff137ce7e56bbd620de191189" +checksum = "68064e60dbf1f17005c2fde4d07c16d8baa506fd7ffed8ccab702d93617975c7" dependencies = [ "jobserver", "libc", @@ -834,9 +834,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.15" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -1021,7 +1021,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.15", + "clap 4.5.16", "criterion-plot", "is-terminal", "itertools 0.10.5", @@ -2428,9 +2428,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.156" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "a5f43f184355eefb8d17fc948dbecf6c13be3c141f20d834ae842193a448c72a" [[package]] name = "libm" @@ -2662,7 +2662,7 @@ name = "nautilus-cli" version = "0.29.0" dependencies = [ "anyhow", - "clap 4.5.15", + "clap 4.5.16", "clap_derive", "dotenvy", "log", @@ -4205,18 +4205,18 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.207" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", diff --git a/nautilus_core/Cargo.toml b/nautilus_core/Cargo.toml index 31b7b3fc4027..91162b022f75 100644 --- a/nautilus_core/Cargo.toml +++ b/nautilus_core/Cargo.toml @@ -44,7 +44,7 @@ rmp-serde = "1.3.0" rust_decimal = "1.35.0" rust_decimal_macros = "1.35.0" semver = "1.0.23" -serde = { version = "1.0.207", features = ["derive"] } +serde = { version = "1.0.208", features = ["derive"] } serde_json = "1.0.125" strum = { version = "0.26.3", features = ["derive"] } thiserror = "1.0.63" diff --git a/nautilus_core/cli/Cargo.toml b/nautilus_core/cli/Cargo.toml index 149ce529b4b5..2e3cf56a90a5 100644 --- a/nautilus_core/cli/Cargo.toml +++ b/nautilus_core/cli/Cargo.toml @@ -18,7 +18,7 @@ nautilus-infrastructure = { path = "../infrastructure" , features = ["postgres"] anyhow = { workspace = true } log = { workspace = true } tokio = {workspace = true} -clap = { version = "4.5.15", features = ["derive", "env"] } +clap = { version = "4.5.16", features = ["derive", "env"] } clap_derive = { version = "4.5.13" } dotenvy = { version = "0.15.7" } simple_logger = "5.0.0" From fcd3aeb888554e2d2df2d151c7a4b4693125f90d Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Fri, 16 Aug 2024 18:27:51 +1000 Subject: [PATCH 45/72] Upgrade uvloop --- RELEASES.md | 1 + poetry.lock | 110 ++++++++++++++++++++++++------------------------- pyproject.toml | 4 +- 3 files changed, 58 insertions(+), 57 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index fce25cf1016a..e3da813ce9d8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,6 +10,7 @@ Released on TBD (UTC). - Improved `@customdataclass` decorator constructor to allow more positional arguments, thanks @faysou - Refined error modeling and handling in Rust (#1849), thanks @twitu - Upgraded `datafusion` crate to v41.0.0 +- Upgraded `uvloop` to v0.20.0 ### Breaking Changes - Changed `VolumeWeightedAveragePrice` calculation formula to use each bars "typical" price (#1842), thanks @evgenii-prusov diff --git a/poetry.lock b/poetry.lock index 3739363e5aaf..d6b67ed3a6b4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "aiohappyeyeballs" -version = "2.3.5" +version = "2.3.6" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.3.5-py3-none-any.whl", hash = "sha256:4d6dea59215537dbc746e93e779caea8178c866856a721c9c660d7a5a7b8be03"}, - {file = "aiohappyeyeballs-2.3.5.tar.gz", hash = "sha256:6fa48b9f1317254f122a07a131a86b71ca6946ca989ce6326fff54a99a920105"}, + {file = "aiohappyeyeballs-2.3.6-py3-none-any.whl", hash = "sha256:15dca2611fa78442f1cb54cf07ffb998573f2b4fbeab45ca8554c045665c896b"}, + {file = "aiohappyeyeballs-2.3.6.tar.gz", hash = "sha256:88211068d2a40e0436033956d7de3926ff36d54776f8b1022d6b21320cadae79"}, ] [[package]] @@ -1838,29 +1838,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.5.7" +version = "0.6.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, - {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, - {file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"}, - {file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"}, - {file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"}, - {file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"}, - {file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"}, - {file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"}, - {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"}, + {file = "ruff-0.6.0-py3-none-linux_armv6l.whl", hash = "sha256:92dcce923e5df265781e5fc76f9a1edad52201a7aafe56e586b90988d5239013"}, + {file = "ruff-0.6.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:31b90ff9dc79ed476c04e957ba7e2b95c3fceb76148f2079d0d68a908d2cfae7"}, + {file = "ruff-0.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d834a9ec9f8287dd6c3297058b3a265ed6b59233db22593379ee38ebc4b9768"}, + {file = "ruff-0.6.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2089267692696aba342179471831a085043f218706e642564812145df8b8d0d"}, + {file = "ruff-0.6.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa62b423ee4bbd8765f2c1dbe8f6aac203e0583993a91453dc0a449d465c84da"}, + {file = "ruff-0.6.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7344e1a964b16b1137ea361d6516ce4ee61a0403fa94252a1913ecc1311adcae"}, + {file = "ruff-0.6.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:487f3a35c3f33bf82be212ce15dc6278ea854e35573a3f809442f73bec8b2760"}, + {file = "ruff-0.6.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75db409984077a793cf344d499165298a6f65449e905747ac65983b12e3e64b1"}, + {file = "ruff-0.6.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84908bd603533ecf1db456d8fc2665d1f4335d722e84bc871d3bbd2d1116c272"}, + {file = "ruff-0.6.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f1749a0aef3ec41ed91a0e2127a6ae97d2e2853af16dbd4f3c00d7a3af726c5"}, + {file = "ruff-0.6.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:016fea751e2bcfbbd2f8cb19b97b37b3fd33148e4df45b526e87096f4e17354f"}, + {file = "ruff-0.6.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6ae80f141b53b2e36e230017e64f5ea2def18fac14334ffceaae1b780d70c4f7"}, + {file = "ruff-0.6.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eaaaf33ea4b3f63fd264d6a6f4a73fa224bbfda4b438ffea59a5340f4afa2bb5"}, + {file = "ruff-0.6.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7667ddd1fc688150a7ca4137140867584c63309695a30016880caf20831503a0"}, + {file = "ruff-0.6.0-py3-none-win32.whl", hash = "sha256:ae48365aae60d40865a412356f8c6f2c0be1c928591168111eaf07eaefa6bea3"}, + {file = "ruff-0.6.0-py3-none-win_amd64.whl", hash = "sha256:774032b507c96f0c803c8237ce7d2ef3934df208a09c40fa809c2931f957fe5e"}, + {file = "ruff-0.6.0-py3-none-win_arm64.whl", hash = "sha256:a5366e8c3ae6b2dc32821749b532606c42e609a99b0ae1472cf601da931a048c"}, + {file = "ruff-0.6.0.tar.gz", hash = "sha256:272a81830f68f9bd19d49eaf7fa01a5545c5a2e86f32a9935bb0e4bb9a1db5b8"}, ] [[package]] @@ -2225,42 +2225,42 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvloop" -version = "0.19.0" +version = "0.20.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, + {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996"}, + {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b"}, + {file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10"}, + {file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae"}, + {file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006"}, + {file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73"}, + {file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037"}, + {file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9"}, + {file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e"}, + {file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756"}, + {file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0"}, + {file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf"}, + {file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d"}, + {file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e"}, + {file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9"}, + {file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab"}, + {file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5"}, + {file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00"}, + {file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0e94b221295b5e69de57a1bd4aeb0b3a29f61be6e1b478bb8a69a73377db7ba"}, + {file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fee6044b64c965c425b65a4e17719953b96e065c5b7e09b599ff332bb2744bdf"}, + {file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:265a99a2ff41a0fd56c19c3838b29bf54d1d177964c300dad388b27e84fd7847"}, + {file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10c2956efcecb981bf9cfb8184d27d5d64b9033f917115a960b83f11bfa0d6b"}, + {file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e7d61fe8e8d9335fac1bf8d5d82820b4808dd7a43020c149b63a1ada953d48a6"}, + {file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2beee18efd33fa6fdb0976e18475a4042cd31c7433c866e8a09ab604c7c22ff2"}, + {file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8c36fdf3e02cec92aed2d44f63565ad1522a499c654f07935c8f9d04db69e95"}, + {file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0fac7be202596c7126146660725157d4813aa29a4cc990fe51346f75ff8fde7"}, + {file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d0fba61846f294bce41eb44d60d58136090ea2b5b99efd21cbdf4e21927c56a"}, + {file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95720bae002ac357202e0d866128eb1ac82545bcf0b549b9abe91b5178d9b541"}, + {file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:36c530d8fa03bfa7085af54a48f2ca16ab74df3ec7108a46ba82fd8b411a2315"}, + {file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e97152983442b499d7a71e44f29baa75b3b02e65d9c44ba53b10338e98dedb66"}, + {file = "uvloop-0.20.0.tar.gz", hash = "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469"}, ] [package.extras] @@ -2398,4 +2398,4 @@ ib = ["async-timeout", "defusedxml", "nautilus_ibapi"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "f2fdd11db584bedddd37adcbeec0be1ef10993cfb5db1f750649b058182d9a6d" +content-hash = "6a41994283c2a666b915cd6a9364bebc68665b910156b0d5e5a8cd51dbaa82ff" diff --git a/pyproject.toml b/pyproject.toml index 14fa58a64c2f..d0136242ddbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ pandas = "^2.2.2" pyarrow = ">=17.0.0" pytz = ">=2024.1.0" tqdm = "^4.66.5" -uvloop = {version = "^0.19.0", markers = "sys_platform != 'win32'"} +uvloop = {version = "^0.20.0", markers = "sys_platform != 'win32'"} async-timeout = {version = "^4.0.3", optional = true} betfair_parser = {version = "==0.13.0", optional = true} # Pinned for stability @@ -84,7 +84,7 @@ docformatter = "^1.7.5" mypy = "^1.11.1" pandas-stubs = "^2.2.2" pre-commit = "^3.8.0" -ruff = "^0.5.7" +ruff = "^0.6.0" types-pytz = "^2024.1" types-requests = "^2.32" types-toml = "^0.10.2" From 6021cf477a1e12e596b164f2a23f674c673e79ac Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 17 Aug 2024 08:22:10 +1000 Subject: [PATCH 46/72] Check and refine tutorial notebooks --- .pre-commit-config.yaml | 2 +- .../getting_started/backtest_high_level.ipynb | 33 ++++----- docs/getting_started/backtest_low_level.ipynb | 7 +- docs/getting_started/quickstart.ipynb | 70 ++++++++----------- .../backtest_binance_orderbook.ipynb | 36 ++++++---- docs/tutorials/backtest_fx_bars.ipynb | 12 ++-- docs/tutorials/databento_data_catalog.ipynb | 54 ++++++++------ docs/tutorials/loading_external_data.ipynb | 34 +++++---- nautilus_core/Cargo.lock | 4 +- poetry.lock | 40 +++++------ pyproject.toml | 2 +- 11 files changed, 147 insertions(+), 147 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d9f30b884e45..9a243f79507e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -83,7 +83,7 @@ repos: exclude: "docs/_pygments/monokai.py" - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.0 + rev: v0.6.1 hooks: - id: ruff args: ["--fix"] diff --git a/docs/getting_started/backtest_high_level.ipynb b/docs/getting_started/backtest_high_level.ipynb index fcb46e3aea35..55eff99b67dc 100644 --- a/docs/getting_started/backtest_high_level.ipynb +++ b/docs/getting_started/backtest_high_level.ipynb @@ -34,9 +34,9 @@ "metadata": {}, "source": [ "## Prerequisites\n", - "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)\n", + "- Python 3.10+ installed\n", "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`)\n", - "- Python 3.10+ installed" + "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)" ] }, { @@ -56,19 +56,20 @@ "metadata": {}, "outputs": [], "source": [ - "import datetime\n", "import shutil\n", "from decimal import Decimal\n", "from pathlib import Path\n", "\n", - "import fsspec\n", "import pandas as pd\n", "\n", - "from nautilus_trader.backtest.node import BacktestNode, BacktestVenueConfig, BacktestDataConfig, BacktestRunConfig, BacktestEngineConfig\n", - "from nautilus_trader.core.datetime import dt_to_unix_nanos\n", + "from nautilus_trader.backtest.node import BacktestDataConfig\n", + "from nautilus_trader.backtest.node import BacktestEngineConfig\n", + "from nautilus_trader.backtest.node import BacktestNode\n", + "from nautilus_trader.backtest.node import BacktestRunConfig\n", + "from nautilus_trader.backtest.node import BacktestVenueConfig\n", "from nautilus_trader.config import ImportableStrategyConfig\n", + "from nautilus_trader.core.datetime import dt_to_unix_nanos\n", "from nautilus_trader.model.data import QuoteTick\n", - "from nautilus_trader.model.objects import Price, Quantity\n", "from nautilus_trader.persistence.catalog import ParquetDataCatalog\n", "from nautilus_trader.persistence.wranglers import QuoteTickDataWrangler\n", "from nautilus_trader.test_kit.providers import CSVTickDataLoader\n", @@ -84,7 +85,7 @@ "\n", "For this example we will use FX data from `histdata.com`. Simply go to https://www.histdata.com/download-free-forex-historical-data/?/ascii/tick-data-quotes/ and select an FX pair, then select one or more months of data to download.\n", "\n", - "Once you have downloaded the data, set the variable `DATA_DIR` below to the directory containing the data. By default, it will use the users `Downloads` directory." + "Once you have downloaded the data, set the variable `DATA_DIR` below to the directory containing the data. By default, it will use the users `Downloads/Data/` directory." ] }, { @@ -94,7 +95,7 @@ "metadata": {}, "outputs": [], "source": [ - "DATA_DIR = \"~/Downloads/\"" + "DATA_DIR = \"~/Downloads/Data/\"" ] }, { @@ -283,13 +284,13 @@ " ImportableStrategyConfig(\n", " strategy_path=\"nautilus_trader.examples.strategies.ema_cross:EMACross\",\n", " config_path=\"nautilus_trader.examples.strategies.ema_cross:EMACrossConfig\",\n", - " config=dict(\n", - " instrument_id=instrument.id,\n", - " bar_type=\"EUR/USD.SIM-15-MINUTE-BID-INTERNAL\",\n", - " fast_ema_period=10,\n", - " slow_ema_period=20,\n", - " trade_size=Decimal(1_000_000),\n", - " ),\n", + " config={\n", + " \"instrument_id\": instrument.id,\n", + " \"bar_type\": \"EUR/USD.SIM-15-MINUTE-BID-INTERNAL\",\n", + " \"fast_ema_period\": 10,\n", + " \"slow_ema_period\": 20,\n", + " \"trade_size\": Decimal(1_000_000),\n", + " },\n", " ),\n", "]" ] diff --git a/docs/getting_started/backtest_low_level.ipynb b/docs/getting_started/backtest_low_level.ipynb index fd42c0acf11a..d82f8d515903 100644 --- a/docs/getting_started/backtest_low_level.ipynb +++ b/docs/getting_started/backtest_low_level.ipynb @@ -36,9 +36,9 @@ "metadata": {}, "source": [ "## Prerequisites\n", - "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)\n", + "- Python 3.10+ installed\n", "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`)\n", - "- Python 3.10+ installed" + "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)" ] }, { @@ -58,11 +58,8 @@ "metadata": {}, "outputs": [], "source": [ - "import time\n", "from decimal import Decimal\n", "\n", - "import pandas as pd\n", - "\n", "from nautilus_trader.backtest.engine import BacktestEngine\n", "from nautilus_trader.backtest.engine import BacktestEngineConfig\n", "from nautilus_trader.examples.algorithms.twap import TWAPExecAlgorithm\n", diff --git a/docs/getting_started/quickstart.ipynb b/docs/getting_started/quickstart.ipynb index 16c93a75b301..2967aa392329 100644 --- a/docs/getting_started/quickstart.ipynb +++ b/docs/getting_started/quickstart.ipynb @@ -29,9 +29,9 @@ "metadata": {}, "source": [ "## Prerequisites\n", + "- Python 3.10+ installed\n", "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)\n", - "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`)\n", - "- Python 3.10+ installed" + "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`)" ] }, { @@ -61,26 +61,16 @@ }, "outputs": [], "source": [ - "import datetime\n", - "import os\n", - "import shutil\n", - "from decimal import Decimal\n", - "\n", - "import fsspec\n", - "import pandas as pd\n", - "\n", - "from nautilus_trader.backtest.node import BacktestNode\n", - "from nautilus_trader.backtest.node import BacktestVenueConfig\n", "from nautilus_trader.backtest.node import BacktestDataConfig\n", - "from nautilus_trader.backtest.node import BacktestRunConfig\n", "from nautilus_trader.backtest.node import BacktestEngineConfig\n", + "from nautilus_trader.backtest.node import BacktestNode\n", + "from nautilus_trader.backtest.node import BacktestRunConfig\n", + "from nautilus_trader.backtest.node import BacktestVenueConfig\n", "from nautilus_trader.config import ImportableStrategyConfig\n", "from nautilus_trader.config import LoggingConfig\n", - "from nautilus_trader.core.datetime import dt_to_unix_nanos\n", "from nautilus_trader.model.data import QuoteTick\n", - "from nautilus_trader.model.objects import Price, Quantity\n", - "from nautilus_trader.persistence.catalog import ParquetDataCatalog\n", - "from nautilus_trader.test_kit.providers import TestInstrumentProvider" + "from nautilus_trader.model.objects import Quantity\n", + "from nautilus_trader.persistence.catalog import ParquetDataCatalog" ] }, { @@ -100,8 +90,6 @@ "metadata": {}, "outputs": [], "source": [ - "from nautilus_trader.persistence.catalog import ParquetDataCatalog\n", - "\n", "# You can also use a relative path such as `ParquetDataCatalog(\"./catalog\")`,\n", "# for example if you're running this notebook after the data setup from the docs.\n", "# catalog = ParquetDataCatalog(\"./catalog\")\n", @@ -134,15 +122,14 @@ "source": [ "from nautilus_trader.core.message import Event\n", "from nautilus_trader.indicators.macd import MovingAverageConvergenceDivergence\n", - "from nautilus_trader.model.data import QuoteTick\n", - "from nautilus_trader.model.enums import PriceType\n", - "from nautilus_trader.model.enums import PositionSide\n", "from nautilus_trader.model.enums import OrderSide\n", + "from nautilus_trader.model.enums import PositionSide\n", + "from nautilus_trader.model.enums import PriceType\n", "from nautilus_trader.model.events import PositionOpened\n", "from nautilus_trader.model.identifiers import InstrumentId\n", - "from nautilus_trader.model.objects import Quantity\n", "from nautilus_trader.model.position import Position\n", - "from nautilus_trader.trading.strategy import Strategy, StrategyConfig\n", + "from nautilus_trader.trading.strategy import Strategy\n", + "from nautilus_trader.trading.strategy import StrategyConfig\n", "\n", "\n", "class MACDConfig(StrategyConfig):\n", @@ -152,7 +139,7 @@ " trade_size: int = 1_000_000\n", " entry_threshold: float = 0.00010\n", "\n", - " \n", + "\n", "class MACDStrategy(Strategy):\n", " def __init__(self, config: MACDConfig):\n", " super().__init__(config=config)\n", @@ -183,7 +170,7 @@ "\n", " if not self.macd.initialized:\n", " return # Wait for indicator to warm up\n", - " \n", + "\n", " # self._log.info(f\"{self.macd.value=}:%5d\")\n", " self.check_for_entry()\n", " self.check_for_exit()\n", @@ -268,8 +255,6 @@ "metadata": {}, "outputs": [], "source": [ - "from nautilus_trader.config import BacktestVenueConfig\n", - "\n", "venue = BacktestVenueConfig(\n", " name=\"SIM\",\n", " oms_type=\"NETTING\",\n", @@ -319,9 +304,9 @@ "metadata": {}, "outputs": [], "source": [ - "from nautilus_trader.config import BacktestDataConfig\n", "from nautilus_trader.model.data import QuoteTick\n", "\n", + "\n", "data = BacktestDataConfig(\n", " catalog_path=str(catalog.path),\n", " data_cls=QuoteTick,\n", @@ -352,10 +337,6 @@ "metadata": {}, "outputs": [], "source": [ - "from nautilus_trader.config import BacktestEngineConfig\n", - "from nautilus_trader.config import ImportableStrategyConfig\n", - "from nautilus_trader.config import LoggingConfig\n", - "\n", "# NautilusTrader currently exceeds the rate limit for Jupyter notebook logging (stdout output),\n", "# this is why the `log_level` is set to \"ERROR\". If you lower this level to see\n", "# more logging then the notebook will hang during cell execution. A fix is currently\n", @@ -368,11 +349,11 @@ " ImportableStrategyConfig(\n", " strategy_path=\"__main__:MACDStrategy\",\n", " config_path=\"__main__:MACDConfig\",\n", - " config=dict(\n", - " instrument_id=instruments[0].id,\n", - " fast_period=12,\n", - " slow_period=26,\n", - " ),\n", + " config={\n", + " \"instrument_id\": instruments[0].id,\n", + " \"fast_period\": 12,\n", + " \"slow_period\": 26,\n", + " },\n", " )\n", " ],\n", " logging=LoggingConfig(log_level=\"ERROR\"),\n", @@ -397,9 +378,6 @@ "metadata": {}, "outputs": [], "source": [ - "from nautilus_trader.config import BacktestRunConfig\n", - "\n", - "\n", "config = BacktestRunConfig(\n", " engine=engine,\n", " venues=[venue],\n", @@ -424,7 +402,6 @@ "metadata": {}, "outputs": [], "source": [ - "from nautilus_trader.backtest.node import BacktestNode\n", "from nautilus_trader.backtest.results import BacktestResult\n", "\n", "\n", @@ -463,6 +440,7 @@ "from nautilus_trader.backtest.engine import BacktestEngine\n", "from nautilus_trader.model.identifiers import Venue\n", "\n", + "\n", "engine: BacktestEngine = node.get_engine(config.id)\n", "\n", "engine.trader.generate_order_fills_report()" @@ -487,6 +465,14 @@ "source": [ "engine.trader.generate_account_report(Venue(\"SIM\"))" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/docs/tutorials/backtest_binance_orderbook.ipynb b/docs/tutorials/backtest_binance_orderbook.ipynb index 08f16f4dca60..3a00cee82c7c 100644 --- a/docs/tutorials/backtest_binance_orderbook.ipynb +++ b/docs/tutorials/backtest_binance_orderbook.ipynb @@ -33,9 +33,9 @@ "source": [ "## Prerequisites\n", "\n", - "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)\n", + "- Python 3.10+ installed\n", "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`)\n", - "- Python 3.10+ installed" + "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)" ] }, { @@ -62,19 +62,18 @@ "\n", "import pandas as pd\n", "\n", - "from nautilus_trader.backtest.node import BacktestNode\n", - "from nautilus_trader.backtest.node import BacktestVenueConfig\n", "from nautilus_trader.backtest.node import BacktestDataConfig\n", - "from nautilus_trader.backtest.node import BacktestRunConfig\n", "from nautilus_trader.backtest.node import BacktestEngineConfig\n", - "from nautilus_trader.core.datetime import dt_to_unix_nanos\n", + "from nautilus_trader.backtest.node import BacktestNode\n", + "from nautilus_trader.backtest.node import BacktestRunConfig\n", + "from nautilus_trader.backtest.node import BacktestVenueConfig\n", "from nautilus_trader.config import ImportableStrategyConfig\n", "from nautilus_trader.config import LoggingConfig\n", - "from nautilus_trader.examples.strategies.ema_cross import EMACross, EMACrossConfig\n", + "from nautilus_trader.core.datetime import dt_to_unix_nanos\n", "from nautilus_trader.model.data import OrderBookDelta\n", + "from nautilus_trader.persistence.catalog import ParquetDataCatalog\n", "from nautilus_trader.persistence.loaders import BinanceOrderBookDeltaDataLoader\n", "from nautilus_trader.persistence.wranglers import OrderBookDeltaDataWrangler\n", - "from nautilus_trader.persistence.catalog import ParquetDataCatalog\n", "from nautilus_trader.test_kit.providers import TestInstrumentProvider" ] }, @@ -268,12 +267,12 @@ " ImportableStrategyConfig(\n", " strategy_path=\"nautilus_trader.examples.strategies.orderbook_imbalance:OrderBookImbalance\",\n", " config_path=\"nautilus_trader.examples.strategies.orderbook_imbalance:OrderBookImbalanceConfig\",\n", - " config=dict(\n", - " instrument_id=instrument.id,\n", - " book_type=book_type,\n", - " max_trade_size=Decimal(\"1.000\"),\n", - " min_seconds_between_triggers=1.0,\n", - " ),\n", + " config={\n", + " \"instrument_id\": instrument.id,\n", + " \"book_type\": book_type,\n", + " \"max_trade_size\": Decimal(\"1.000\"),\n", + " \"min_seconds_between_triggers\": 1.0,\n", + " },\n", " ),\n", "]\n", "\n", @@ -336,6 +335,7 @@ "from nautilus_trader.backtest.engine import BacktestEngine\n", "from nautilus_trader.model.identifiers import Venue\n", "\n", + "\n", "engine: BacktestEngine = node.get_engine(config.id)\n", "\n", "engine.trader.generate_order_fills_report()" @@ -360,6 +360,14 @@ "source": [ "engine.trader.generate_account_report(Venue(\"BINANCE\"))" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/docs/tutorials/backtest_fx_bars.ipynb b/docs/tutorials/backtest_fx_bars.ipynb index 9e36582d4b6c..d054d0ddac4f 100644 --- a/docs/tutorials/backtest_fx_bars.ipynb +++ b/docs/tutorials/backtest_fx_bars.ipynb @@ -33,9 +33,9 @@ "source": [ "## Prerequisites\n", "\n", - "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)\n", + "- Python 3.10+ installed\n", "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`)\n", - "- Python 3.10+ installed" + "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)" ] }, { @@ -57,21 +57,19 @@ }, "outputs": [], "source": [ - "import time\n", "from decimal import Decimal\n", "\n", - "import pandas as pd\n", - "\n", "from nautilus_trader.backtest.engine import BacktestEngine\n", "from nautilus_trader.backtest.engine import BacktestEngineConfig\n", "from nautilus_trader.backtest.models import FillModel\n", "from nautilus_trader.backtest.modules import FXRolloverInterestConfig\n", "from nautilus_trader.backtest.modules import FXRolloverInterestModule\n", - "from nautilus_trader.config import RiskEngineConfig\n", "from nautilus_trader.config import LoggingConfig\n", + "from nautilus_trader.config import RiskEngineConfig\n", "from nautilus_trader.examples.strategies.ema_cross import EMACross\n", "from nautilus_trader.examples.strategies.ema_cross import EMACrossConfig\n", - "from nautilus_trader.model.currencies import USD, JPY\n", + "from nautilus_trader.model.currencies import JPY\n", + "from nautilus_trader.model.currencies import USD\n", "from nautilus_trader.model.data import BarType\n", "from nautilus_trader.model.enums import AccountType\n", "from nautilus_trader.model.enums import OmsType\n", diff --git a/docs/tutorials/databento_data_catalog.ipynb b/docs/tutorials/databento_data_catalog.ipynb index 6f14543b245b..f385a1b32b50 100644 --- a/docs/tutorials/databento_data_catalog.ipynb +++ b/docs/tutorials/databento_data_catalog.ipynb @@ -33,9 +33,9 @@ "source": [ "## Prerequisites\n", "\n", - "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)\n", - "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`)\n", "- Python 3.10+ installed\n", + "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`)\n", + "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)\n", "- [databento](https://pypi.org/project/databento/) Python client library installed to make data requests (`pip install -U databento`)\n", "- [Databento](https://databento.com) account" ] @@ -65,6 +65,7 @@ "source": [ "import databento as db\n", "\n", + "\n", "client = db.Historical() # This will use the DATABENTO_API_KEY environment variable (recommended best practice)" ] }, @@ -106,6 +107,7 @@ "outputs": [], "source": [ "from pathlib import Path\n", + "\n", "from databento import DBNStore" ] }, @@ -172,7 +174,7 @@ " schema=\"mbp-10\",\n", " start=\"2023-12-06T14:30:00\",\n", " end=\"2023-12-06T20:30:00\",\n", - " path=path, # <--- Passing a `path` parameter will ensure the data is written to disk\n", + " path=path, # <-- Passing a `path` parameter will ensure the data is written to disk\n", " )" ] }, @@ -256,10 +258,20 @@ "loader = DatabentoDataLoader()" ] }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "Next, we'll load Rust pyo3 objects to write to the catalog (we could use legacy Cython objects, but this is slightly more efficient) by setting `as_legacy_cython=False`.\n", + "\n", + "We also pass an `instrument_id`, which is not required but makes data loading faster as symbology mapping is not required." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "22", + "id": "23", "metadata": {}, "outputs": [], "source": [ @@ -268,15 +280,15 @@ "\n", "depth10 = loader.from_dbn_file(\n", " path=path,\n", - " instrument_id=instrument_id, # Not required but makes data loading faster (symbology mapping not required)\n", - " as_legacy_cython=False, # This will load Rust pyo3 objects to write to the catalog (we could use legacy Cython objects, but this is slightly more efficient)\n", + " instrument_id=instrument_id,\n", + " as_legacy_cython=False,\n", ")" ] }, { "cell_type": "code", "execution_count": null, - "id": "23", + "id": "24", "metadata": {}, "outputs": [], "source": [ @@ -287,7 +299,7 @@ { "cell_type": "code", "execution_count": null, - "id": "24", + "id": "25", "metadata": {}, "outputs": [], "source": [ @@ -298,7 +310,7 @@ }, { "cell_type": "markdown", - "id": "25", + "id": "26", "metadata": {}, "source": [ "## Preparing a month of AAPL trades" @@ -306,7 +318,7 @@ }, { "cell_type": "markdown", - "id": "26", + "id": "27", "metadata": {}, "source": [ "Now we'll expand on this workflow by preparing a month of AAPL trades on the Nasdaq exchange using the Databento `trade` schema, which will translate to Nautilus `TradeTick` objects." @@ -315,7 +327,7 @@ { "cell_type": "code", "execution_count": null, - "id": "27", + "id": "28", "metadata": {}, "outputs": [], "source": [ @@ -330,7 +342,7 @@ }, { "cell_type": "markdown", - "id": "28", + "id": "29", "metadata": {}, "source": [ "When requesting historical data with the Databento `Historical` data client, ensure you pass a `path` parameter to write the data to disk." @@ -339,7 +351,7 @@ { "cell_type": "code", "execution_count": null, - "id": "29", + "id": "30", "metadata": {}, "outputs": [], "source": [ @@ -358,7 +370,7 @@ }, { "cell_type": "markdown", - "id": "30", + "id": "31", "metadata": {}, "source": [ "Inspect the data by reading from disk and convert to a pandas.DataFrame" @@ -367,7 +379,7 @@ { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -379,7 +391,7 @@ }, { "cell_type": "markdown", - "id": "32", + "id": "33", "metadata": {}, "source": [ "We'll use an `InstrumentId` of `\"AAPL.XNAS\"`, where XNAS is the ISO 10383 MIC (Market Identifier Code) for the Nasdaq venue.\n", @@ -390,7 +402,7 @@ { "cell_type": "code", "execution_count": null, - "id": "33", + "id": "34", "metadata": {}, "outputs": [], "source": [ @@ -405,7 +417,7 @@ }, { "cell_type": "markdown", - "id": "34", + "id": "35", "metadata": {}, "source": [ "Here we'll organize our data as a file per month, this is an arbitrary choice as a file per day could be just as valid.\n", @@ -416,7 +428,7 @@ { "cell_type": "code", "execution_count": null, - "id": "35", + "id": "36", "metadata": {}, "outputs": [], "source": [ @@ -427,7 +439,7 @@ { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "37", "metadata": {}, "outputs": [], "source": [ @@ -437,7 +449,7 @@ { "cell_type": "code", "execution_count": null, - "id": "37", + "id": "38", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/tutorials/loading_external_data.ipynb b/docs/tutorials/loading_external_data.ipynb index 5f4b0747d459..0b9db6c7b02c 100644 --- a/docs/tutorials/loading_external_data.ipynb +++ b/docs/tutorials/loading_external_data.ipynb @@ -23,24 +23,22 @@ "metadata": {}, "outputs": [], "source": [ - "import datetime\n", "import os\n", "import shutil\n", "from decimal import Decimal\n", "from pathlib import Path\n", "\n", - "import fsspec\n", "import pandas as pd\n", - "from nautilus_trader.core.datetime import dt_to_unix_nanos\n", - "from nautilus_trader.backtest.node import BacktestNode\n", - "from nautilus_trader.backtest.node import BacktestVenueConfig\n", + "\n", "from nautilus_trader.backtest.node import BacktestDataConfig\n", - "from nautilus_trader.backtest.node import BacktestRunConfig\n", "from nautilus_trader.backtest.node import BacktestEngineConfig\n", + "from nautilus_trader.backtest.node import BacktestNode\n", + "from nautilus_trader.backtest.node import BacktestRunConfig\n", + "from nautilus_trader.backtest.node import BacktestVenueConfig\n", "from nautilus_trader.config import ImportableStrategyConfig\n", - "from nautilus_trader.model.data import QuoteTick\n", + "from nautilus_trader.core.datetime import dt_to_unix_nanos\n", "from nautilus_trader.model.data import BarType\n", - "from nautilus_trader.model.objects import Price, Quantity\n", + "from nautilus_trader.model.data import QuoteTick\n", "from nautilus_trader.persistence.catalog import ParquetDataCatalog\n", "from nautilus_trader.persistence.wranglers import QuoteTickDataWrangler\n", "from nautilus_trader.test_kit.providers import CSVTickDataLoader\n", @@ -54,7 +52,7 @@ "metadata": {}, "outputs": [], "source": [ - "DATA_DIR = \"~/Downloads\"" + "DATA_DIR = \"~/Downloads/Data/\"" ] }, { @@ -78,8 +76,8 @@ "outputs": [], "source": [ "# Here we just take the first data file found and load into a pandas DataFrame\n", - "df = CSVTickDataLoader.load(raw_files[0], index_col=0, format=\"%Y%m%d %H%M%S%f\")\n", - "df.columns = [\"bid_price\", \"ask_price\"]\n", + "df = CSVTickDataLoader.load(raw_files[0], index_col=0, datetime_format=\"%Y%m%d %H%M%S%f\")\n", + "df.columns = [\"timestamp\", \"bid_price\", \"ask_price\"]\n", "\n", "# Process quote ticks using a wrangler\n", "EURUSD = TestInstrumentProvider.default_fx_ccy(\"EUR/USD\")\n", @@ -176,13 +174,13 @@ " ImportableStrategyConfig(\n", " strategy_path=\"nautilus_trader.examples.strategies.ema_cross:EMACross\",\n", " config_path=\"nautilus_trader.examples.strategies.ema_cross:EMACrossConfig\",\n", - " config=dict(\n", - " instrument_id=instrument.id,\n", - " bar_type=BarType.from_str(f\"{instrument.id.value}-15-MINUTE-BID-INTERNAL\"),\n", - " fast_ema_period=10,\n", - " slow_ema_period=20,\n", - " trade_size=Decimal(1_000_000),\n", - " ),\n", + " config={\n", + " \"instrument_id\": instrument.id,\n", + " \"bar_type\": BarType.from_str(f\"{instrument.id.value}-15-MINUTE-BID-INTERNAL\"),\n", + " \"fast_ema_period\": 10,\n", + " \"slow_ema_period\": 20,\n", + " \"trade_size\": Decimal(1_000_000),\n", + " },\n", " ),\n", "]\n", "\n", diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index f95ffab195ad..b2265c663ddd 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -733,9 +733,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68064e60dbf1f17005c2fde4d07c16d8baa506fd7ffed8ccab702d93617975c7" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" dependencies = [ "jobserver", "libc", diff --git a/poetry.lock b/poetry.lock index d6b67ed3a6b4..86873413c1bb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1838,29 +1838,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.6.0" +version = "0.6.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.0-py3-none-linux_armv6l.whl", hash = "sha256:92dcce923e5df265781e5fc76f9a1edad52201a7aafe56e586b90988d5239013"}, - {file = "ruff-0.6.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:31b90ff9dc79ed476c04e957ba7e2b95c3fceb76148f2079d0d68a908d2cfae7"}, - {file = "ruff-0.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d834a9ec9f8287dd6c3297058b3a265ed6b59233db22593379ee38ebc4b9768"}, - {file = "ruff-0.6.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2089267692696aba342179471831a085043f218706e642564812145df8b8d0d"}, - {file = "ruff-0.6.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa62b423ee4bbd8765f2c1dbe8f6aac203e0583993a91453dc0a449d465c84da"}, - {file = "ruff-0.6.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7344e1a964b16b1137ea361d6516ce4ee61a0403fa94252a1913ecc1311adcae"}, - {file = "ruff-0.6.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:487f3a35c3f33bf82be212ce15dc6278ea854e35573a3f809442f73bec8b2760"}, - {file = "ruff-0.6.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75db409984077a793cf344d499165298a6f65449e905747ac65983b12e3e64b1"}, - {file = "ruff-0.6.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84908bd603533ecf1db456d8fc2665d1f4335d722e84bc871d3bbd2d1116c272"}, - {file = "ruff-0.6.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f1749a0aef3ec41ed91a0e2127a6ae97d2e2853af16dbd4f3c00d7a3af726c5"}, - {file = "ruff-0.6.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:016fea751e2bcfbbd2f8cb19b97b37b3fd33148e4df45b526e87096f4e17354f"}, - {file = "ruff-0.6.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6ae80f141b53b2e36e230017e64f5ea2def18fac14334ffceaae1b780d70c4f7"}, - {file = "ruff-0.6.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eaaaf33ea4b3f63fd264d6a6f4a73fa224bbfda4b438ffea59a5340f4afa2bb5"}, - {file = "ruff-0.6.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7667ddd1fc688150a7ca4137140867584c63309695a30016880caf20831503a0"}, - {file = "ruff-0.6.0-py3-none-win32.whl", hash = "sha256:ae48365aae60d40865a412356f8c6f2c0be1c928591168111eaf07eaefa6bea3"}, - {file = "ruff-0.6.0-py3-none-win_amd64.whl", hash = "sha256:774032b507c96f0c803c8237ce7d2ef3934df208a09c40fa809c2931f957fe5e"}, - {file = "ruff-0.6.0-py3-none-win_arm64.whl", hash = "sha256:a5366e8c3ae6b2dc32821749b532606c42e609a99b0ae1472cf601da931a048c"}, - {file = "ruff-0.6.0.tar.gz", hash = "sha256:272a81830f68f9bd19d49eaf7fa01a5545c5a2e86f32a9935bb0e4bb9a1db5b8"}, + {file = "ruff-0.6.1-py3-none-linux_armv6l.whl", hash = "sha256:b4bb7de6a24169dc023f992718a9417380301b0c2da0fe85919f47264fb8add9"}, + {file = "ruff-0.6.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:45efaae53b360c81043e311cdec8a7696420b3d3e8935202c2846e7a97d4edae"}, + {file = "ruff-0.6.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bc60c7d71b732c8fa73cf995efc0c836a2fd8b9810e115be8babb24ae87e0850"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c7477c3b9da822e2db0b4e0b59e61b8a23e87886e727b327e7dcaf06213c5cf"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a0af7ab3f86e3dc9f157a928e08e26c4b40707d0612b01cd577cc84b8905cc9"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392688dbb50fecf1bf7126731c90c11a9df1c3a4cdc3f481b53e851da5634fa5"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5278d3e095ccc8c30430bcc9bc550f778790acc211865520f3041910a28d0024"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe6d5f65d6f276ee7a0fc50a0cecaccb362d30ef98a110f99cac1c7872df2f18"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e0dd11e2ae553ee5c92a81731d88a9883af8db7408db47fc81887c1f8b672e"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d812615525a34ecfc07fd93f906ef5b93656be01dfae9a819e31caa6cfe758a1"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faaa4060f4064c3b7aaaa27328080c932fa142786f8142aff095b42b6a2eb631"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99d7ae0df47c62729d58765c593ea54c2546d5de213f2af2a19442d50a10cec9"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9eb18dfd7b613eec000e3738b3f0e4398bf0153cb80bfa3e351b3c1c2f6d7b15"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c62bc04c6723a81e25e71715aa59489f15034d69bf641df88cb38bdc32fd1dbb"}, + {file = "ruff-0.6.1-py3-none-win32.whl", hash = "sha256:9fb4c4e8b83f19c9477a8745e56d2eeef07a7ff50b68a6998f7d9e2e3887bdc4"}, + {file = "ruff-0.6.1-py3-none-win_amd64.whl", hash = "sha256:c2ebfc8f51ef4aca05dad4552bbcf6fe8d1f75b2f6af546cc47cc1c1ca916b5b"}, + {file = "ruff-0.6.1-py3-none-win_arm64.whl", hash = "sha256:3bc81074971b0ffad1bd0c52284b22411f02a11a012082a76ac6da153536e014"}, + {file = "ruff-0.6.1.tar.gz", hash = "sha256:af3ffd8c6563acb8848d33cd19a69b9bfe943667f0419ca083f8ebe4224a3436"}, ] [[package]] @@ -2398,4 +2398,4 @@ ib = ["async-timeout", "defusedxml", "nautilus_ibapi"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "6a41994283c2a666b915cd6a9364bebc68665b910156b0d5e5a8cd51dbaa82ff" +content-hash = "f24652140907b098e8c95633d6a14225c27e221f770271215072ea58d59164a5" diff --git a/pyproject.toml b/pyproject.toml index d0136242ddbd..60130f757c4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ docformatter = "^1.7.5" mypy = "^1.11.1" pandas-stubs = "^2.2.2" pre-commit = "^3.8.0" -ruff = "^0.6.0" +ruff = "^0.6.1" types-pytz = "^2024.1" types-requests = "^2.32" types-toml = "^0.10.2" From b320902fff16411a87dfec084f7d8a30527976d8 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 17 Aug 2024 09:42:00 +1000 Subject: [PATCH 47/72] Implement LogLevel::TRACE for Rust --- RELEASES.md | 1 + nautilus_core/common/src/enums.rs | 12 ++++++++---- nautilus_core/common/src/logging/logger.rs | 5 ++++- nautilus_core/common/src/logging/mod.rs | 1 + nautilus_trader/common/component.pyx | 8 ++++---- nautilus_trader/core/includes/common.h | 12 ++++++++---- nautilus_trader/core/rust/common.pxd | 10 ++++++---- tests/unit_tests/common/test_logging.py | 14 ++++++++------ 8 files changed, 40 insertions(+), 23 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index e3da813ce9d8..2fca946614fd 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,6 +4,7 @@ Released on TBD (UTC). ### Enhancements - Added `LiveExecEngineConfig.generate_missing_orders` reconciliation config option to align internal and external position states +- Added `LogLevel::TRACE` (only available in Rust for debug/development builds) - Improved `BinanceExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improved `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improved `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions diff --git a/nautilus_core/common/src/enums.rs b/nautilus_core/common/src/enums.rs index ab3b96185995..54a8bd6131bd 100644 --- a/nautilus_core/common/src/enums.rs +++ b/nautilus_core/common/src/enums.rs @@ -161,22 +161,26 @@ pub enum LogLevel { #[strum(serialize = "OFF")] #[serde(rename = "OFF")] Off = 0, + /// The **TRACE** trace log level. Only available in Rust for debug/development builds. + #[strum(serialize = "TRACE")] + #[serde(rename = "TRACE")] + Trace = 1, /// The **DEBUG** debug log level. #[strum(serialize = "DEBUG")] #[serde(rename = "DEBUG")] - Debug = 10, + Debug = 2, /// The **INFO** info log level. #[strum(serialize = "INFO")] #[serde(rename = "INFO")] - Info = 20, + Info = 3, /// The **WARNING** warning log level. #[strum(serialize = "WARN", serialize = "WARNING")] #[serde(rename = "WARNING")] - Warning = 30, + Warning = 4, /// The **ERROR** error log level. #[strum(serialize = "ERROR")] #[serde(rename = "ERROR")] - Error = 40, + Error = 5, } /// The log color for log messages. diff --git a/nautilus_core/common/src/logging/logger.rs b/nautilus_core/common/src/logging/logger.rs index 2511aea8a39e..d081f6e220a2 100644 --- a/nautilus_core/common/src/logging/logger.rs +++ b/nautilus_core/common/src/logging/logger.rs @@ -350,7 +350,7 @@ impl Logger { .expect("Error spawning `logging` thread"), ); - let max_level = log::LevelFilter::Debug; + let max_level = log::LevelFilter::Trace; set_max_level(max_level); if print_config { println!("Logger set as `log` implementation with max level {max_level}"); @@ -460,6 +460,9 @@ pub fn log(level: LogLevel, color: LogColor, component: Ustr, message: &str) { match level { LogLevel::Off => {} + LogLevel::Trace => { + log::trace!(component = component.to_value(), color = color; "{}", message); + } LogLevel::Debug => { log::debug!(component = component.to_value(), color = color; "{}", message); } diff --git a/nautilus_core/common/src/logging/mod.rs b/nautilus_core/common/src/logging/mod.rs index 5e95d4a670ce..c3b348d48dc0 100644 --- a/nautilus_core/common/src/logging/mod.rs +++ b/nautilus_core/common/src/logging/mod.rs @@ -139,6 +139,7 @@ pub fn init_logging( pub const fn map_log_level_to_filter(log_level: LogLevel) -> LevelFilter { match log_level { LogLevel::Off => LevelFilter::Off, + LogLevel::Trace => LevelFilter::Trace, LogLevel::Debug => LevelFilter::Debug, LogLevel::Info => LevelFilter::Info, LogLevel::Warning => LevelFilter::Warn, diff --git a/nautilus_trader/common/component.pyx b/nautilus_trader/common/component.pyx index 39bd8f5da621..a9255e9975da 100644 --- a/nautilus_trader/common/component.pyx +++ b/nautilus_trader/common/component.pyx @@ -1167,7 +1167,7 @@ cdef class Logger: LogColor color = LogColor.NORMAL, ): """ - Log the given debug level message. + Log the given DEBUG level message. Parameters ---------- @@ -1201,7 +1201,7 @@ cdef class Logger: LogColor color = LogColor.NORMAL, ): """ - Log the given information level message. + Log the given INFO level message. Parameters ---------- @@ -1236,7 +1236,7 @@ cdef class Logger: LogColor color = LogColor.YELLOW, ): """ - Log the given warning level message. + Log the given WARNING level message. Parameters ---------- @@ -1271,7 +1271,7 @@ cdef class Logger: LogColor color = LogColor.RED, ): """ - Log the given error level message. + Log the given ERROR level message. Parameters ---------- diff --git a/nautilus_trader/core/includes/common.h b/nautilus_trader/core/includes/common.h index 944584e8a07c..5dc7861172af 100644 --- a/nautilus_trader/core/includes/common.h +++ b/nautilus_trader/core/includes/common.h @@ -175,22 +175,26 @@ typedef enum LogLevel { * A level lower than all other log levels (off). */ OFF = 0, + /** + * The **TRACE** trace log level. Only available in Rust for debug/development builds. + */ + TRACE = 1, /** * The **DEBUG** debug log level. */ - DEBUG = 10, + DEBUG = 2, /** * The **INFO** info log level. */ - INFO = 20, + INFO = 3, /** * The **WARNING** warning log level. */ - WARNING = 30, + WARNING = 4, /** * The **ERROR** error log level. */ - ERROR = 40, + ERROR = 5, } LogLevel; /** diff --git a/nautilus_trader/core/rust/common.pxd b/nautilus_trader/core/rust/common.pxd index b089e8012350..855b9d9220bf 100644 --- a/nautilus_trader/core/rust/common.pxd +++ b/nautilus_trader/core/rust/common.pxd @@ -92,14 +92,16 @@ cdef extern from "../includes/common.h": cpdef enum LogLevel: # A level lower than all other log levels (off). OFF # = 0, + # The **TRACE** trace log level. Only available in Rust for debug/development builds. + TRACE # = 1, # The **DEBUG** debug log level. - DEBUG # = 10, + DEBUG # = 2, # The **INFO** info log level. - INFO # = 20, + INFO # = 3, # The **WARNING** warning log level. - WARNING # = 30, + WARNING # = 4, # The **ERROR** error log level. - ERROR # = 40, + ERROR # = 5, # A real-time clock which uses system time. # diff --git a/tests/unit_tests/common/test_logging.py b/tests/unit_tests/common/test_logging.py index d0e4702a1add..08cf1d437e97 100644 --- a/tests/unit_tests/common/test_logging.py +++ b/tests/unit_tests/common/test_logging.py @@ -26,6 +26,7 @@ class TestLogLevel: @pytest.mark.parametrize( ("enum", "expected"), [ + [LogLevel.TRACE, "TRACE"], [LogLevel.DEBUG, "DEBUG"], [LogLevel.INFO, "INFO"], [LogLevel.WARNING, "WARNING"], @@ -42,6 +43,7 @@ def test_log_level_to_str(self, enum, expected): @pytest.mark.parametrize( ("string", "expected"), [ + ["TRACE", LogLevel.TRACE], ["DEBUG", LogLevel.DEBUG], ["INFO", LogLevel.INFO], ["WARN", LogLevel.WARNING], @@ -71,7 +73,7 @@ def test_log_debug_messages_to_console(self): logger = Logger(name="TEST_LOGGER") # Act - logger.debug("This is a log message.") + logger.debug("This is a DEBUG log message.") # Assert assert True # No exceptions raised @@ -81,7 +83,7 @@ def test_log_info_messages_to_console(self): logger = Logger(name="TEST_LOGGER") # Act - logger.info("This is a log message.") + logger.info("This is an INFO log message.") # Assert assert True # No exceptions raised @@ -91,7 +93,7 @@ def test_log_info_messages_to_console_with_blue_colour(self): logger = Logger(name="TEST_LOGGER") # Act - logger.info("This is a log message.", color=LogColor.BLUE) + logger.info("This is an INFO log message.", color=LogColor.BLUE) # Assert assert True # No exceptions raised @@ -101,7 +103,7 @@ def test_log_info_messages_to_console_with_green_colour(self): logger = Logger(name="TEST_LOGGER") # Act - logger.info("This is a log message.", color=LogColor.GREEN) + logger.info("This is an INFO log message.", color=LogColor.GREEN) # Assert assert True # No exceptions raised @@ -111,7 +113,7 @@ def test_log_warning_messages_to_console(self): logger = Logger(name="TEST_LOGGER") # Act - logger.warning("This is a log message.") + logger.warning("This is a WARNING log message.") # Assert assert True # No exceptions raised @@ -121,7 +123,7 @@ def test_log_error_messages_to_console(self): logger = Logger(name="TEST_LOGGER") # Act - logger.error("This is a log message.") + logger.error("This is an ERROR log message.") # Assert assert True # No exceptions raised From 47be27a2171d7d96bcdb13fcd8630e19dc0f4887 Mon Sep 17 00:00:00 2001 From: faysou Date: Sat, 17 Aug 2024 02:59:43 +0100 Subject: [PATCH 48/72] Implement subscribe SignalData for Actor (#1853) --- nautilus_trader/common/actor.pxd | 1 + nautilus_trader/common/actor.pyx | 10 ++++++++ nautilus_trader/core/data.pyx | 7 ++++++ nautilus_trader/persistence/writer.py | 8 +++++- tests/unit_tests/common/test_actor.py | 36 +++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/nautilus_trader/common/actor.pxd b/nautilus_trader/common/actor.pxd index 2bfa0e56907a..6f626b23f587 100644 --- a/nautilus_trader/common/actor.pxd +++ b/nautilus_trader/common/actor.pxd @@ -171,6 +171,7 @@ cdef class Actor(Component): cpdef void unsubscribe_instrument_status(self, InstrumentId instrument_id, ClientId client_id=*) cpdef void publish_data(self, DataType data_type, Data data) cpdef void publish_signal(self, str name, value, uint64_t ts_event=*) + cpdef void subscribe_signal(self, str name=*) # -- REQUESTS ------------------------------------------------------------------------------------- diff --git a/nautilus_trader/common/actor.pyx b/nautilus_trader/common/actor.pyx index 810e36a041af..830e18750c02 100644 --- a/nautilus_trader/common/actor.pyx +++ b/nautilus_trader/common/actor.pyx @@ -1841,6 +1841,16 @@ cdef class Actor(Component): ) self.publish_data(data_type=DataType(cls), data=data) + cpdef void subscribe_signal(self, str name = ""): + Condition.not_none(name, "name") + + topic = f"Signal{name.title()}*" + + self._msgbus.subscribe( + topic=f"data.{topic}", + handler=self.handle_data, + ) + # -- REQUESTS ------------------------------------------------------------------------------------- cpdef UUID4 request_data( diff --git a/nautilus_trader/core/data.pyx b/nautilus_trader/core/data.pyx index e10d3f101a43..0d4b0bb7e79c 100644 --- a/nautilus_trader/core/data.pyx +++ b/nautilus_trader/core/data.pyx @@ -65,3 +65,10 @@ cdef class Data: """ return cls.__module__ + ':' + cls.__qualname__ + + @classmethod + def is_signal(cls, str name = "") -> bool: + if name == "": + return cls.__name__.startswith("Signal") + + return cls.__name__ == f"Signal{name.title()}" diff --git a/nautilus_trader/persistence/writer.py b/nautilus_trader/persistence/writer.py index 0119646da09f..486d33e30035 100644 --- a/nautilus_trader/persistence/writer.py +++ b/nautilus_trader/persistence/writer.py @@ -361,7 +361,13 @@ def deserialize_signal(table: pa.Table) -> list[SignalData]: { "ts_event": pa.uint64(), "ts_init": pa.uint64(), - "value": {int: pa.int64(), float: pa.float64(), str: pa.string()}[value_type], + "value": { + int: pa.int64(), + float: pa.float64(), + str: pa.string(), + bool: pa.bool_(), + bytes: pa.binary(), + }[value_type], }, ) register_arrow( diff --git a/tests/unit_tests/common/test_actor.py b/tests/unit_tests/common/test_actor.py index c0785f252b0d..e67da41a4f2a 100644 --- a/tests/unit_tests/common/test_actor.py +++ b/tests/unit_tests/common/test_actor.py @@ -1840,10 +1840,46 @@ def test_publish_signal_sends_to_subscriber(self) -> None: # Assert msg = handler[0] assert isinstance(msg, Data) + assert msg.is_signal() + assert msg.is_signal("test") assert msg.ts_event == 0 assert msg.ts_init == 0 assert msg.value == value + def test_subscribe_signal(self) -> None: + # Arrange + actor = MockActor() + actor.register_base( + portfolio=self.portfolio, + msgbus=self.msgbus, + cache=self.cache, + clock=self.clock, + ) + + # Act + actor.subscribe_signal("test") + + # Assert + assert self.data_engine.command_count == 0 + assert actor.msgbus.subscriptions()[4].topic == "data.SignalTest*" + + def test_subscribe_all_signals(self) -> None: + # Arrange + actor = MockActor() + actor.register_base( + portfolio=self.portfolio, + msgbus=self.msgbus, + cache=self.cache, + clock=self.clock, + ) + + # Act + actor.subscribe_signal() + + # Assert + assert self.data_engine.command_count == 0 + assert actor.msgbus.subscriptions()[4].topic == "data.Signal*" + def test_publish_data_persist(self) -> None: # Arrange actor = MockActor() From 3c5dbbf3412bc643314e3b46367e104d8e4c2a4e Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 17 Aug 2024 14:26:11 +1000 Subject: [PATCH 49/72] Improve subscribe signal docstrings --- nautilus_trader/common/actor.pyx | 16 +++++++++++++++- nautilus_trader/core/data.pyx | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/nautilus_trader/common/actor.pyx b/nautilus_trader/common/actor.pyx index 830e18750c02..29cca03bdec7 100644 --- a/nautilus_trader/common/actor.pyx +++ b/nautilus_trader/common/actor.pyx @@ -1810,12 +1810,14 @@ cdef class Actor(Component): cpdef void publish_signal(self, str name, value, uint64_t ts_event = 0): """ - Publish the given value as a signal to the message bus. Optionally setup persistence for this `signal`. + Publish the given value as a signal to the message bus. Parameters ---------- name : str The name of the signal being published. + The signal name is case-insensitive and will be capitalized + (e.g., 'example' becomes 'SignalExample'). value : object The signal data to publish. ts_event : uint64_t, optional @@ -1842,6 +1844,18 @@ cdef class Actor(Component): self.publish_data(data_type=DataType(cls), data=data) cpdef void subscribe_signal(self, str name = ""): + """ + Subscribe to a specific signal by name, or to all signals if no name is provided. + + Parameters + ---------- + name : str, optional + The name of the signal to subscribe to. If not provided or an empty + string is passed, the subscription will include all signals. + The signal name is case-insensitive and will be capitalized + (e.g., 'example' becomes 'SignalExample*'). + + """ Condition.not_none(name, "name") topic = f"Signal{name.title()}*" diff --git a/nautilus_trader/core/data.pyx b/nautilus_trader/core/data.pyx index 0d4b0bb7e79c..8a1ac8fb1edb 100644 --- a/nautilus_trader/core/data.pyx +++ b/nautilus_trader/core/data.pyx @@ -68,6 +68,23 @@ cdef class Data: @classmethod def is_signal(cls, str name = "") -> bool: + """ + Determine if the current class is a signal type, optionally checking for a specific signal name. + + Parameters + ---------- + name : str, optional + The specific signal name to check. + If `name` not provided or if an empty string is passed, the method checks whether the + class name indicates a general signal type. + If `name` is provided, the method checks if the class name corresponds to that specific signal. + + Returns + ------- + bool + True if the class name matches the signal type or the specific signal name, otherwise False. + + """ if name == "": return cls.__name__.startswith("Signal") From 426786ec175d23c4f27eb254cdfb18c99135670d Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 17 Aug 2024 14:44:26 +1000 Subject: [PATCH 50/72] Update release notes --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index 2fca946614fd..d264648768ff 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -5,6 +5,7 @@ Released on TBD (UTC). ### Enhancements - Added `LiveExecEngineConfig.generate_missing_orders` reconciliation config option to align internal and external position states - Added `LogLevel::TRACE` (only available in Rust for debug/development builds) +- Added `Actor.subscribe_signal(...)` method and `Data.is_signal(...)` class method (#1853), thanks @faysou - Improved `BinanceExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improved `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improved `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions From df6ccb0b841868d790aeba0eba86e1d2b98b0bfe Mon Sep 17 00:00:00 2001 From: faysou Date: Sat, 17 Aug 2024 10:58:48 +0100 Subject: [PATCH 51/72] Improve custom data documentation (#1854) --- docs/concepts/advanced/custom_data.md | 36 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/docs/concepts/advanced/custom_data.md b/docs/concepts/advanced/custom_data.md index 0bd9a727ac7e..8c33e39af113 100644 --- a/docs/concepts/advanced/custom_data.md +++ b/docs/concepts/advanced/custom_data.md @@ -101,6 +101,22 @@ def on_data(self, data: Data) -> None: # Do something with the data ``` +### Publishing and receiving signal data + +Here is an example of publishing and receiving signal data using the `MessageBus` from an actor or strategy. +A signal is an automatically generated custom data identified by a name containing only one value of a basic type +(str, float, int, bool or bytes). + +```python +self.publish_signal('signal_name', value, ts_event) + +self.subscribe_signal('signal_name') + +def on_data(self, data): + if data.is_signal('signal_name'): + print("Signal", data) +``` + ## Option Greeks example This example demonstrates how to create a custom data type for option Greeks, specifically the delta. @@ -186,7 +202,7 @@ class GreeksData(Data): ### Publishing and receiving data -Here is an example of publishing and receiving data using the `MessageBus` from an actor (which includes strategies): +Here is an example of publishing and receiving data using the `MessageBus` from an actor or strategy: ```python register_serializable_type(GreeksData, GreeksData.to_dict, GreeksData.from_dict) @@ -204,7 +220,7 @@ def on_data(self, data): ### Writing and reading data using the cache -Here is an example of writing and reading data using the `Cache` from an actor (which includes strategies): +Here is an example of writing and reading data using the `Cache` from an actor or strategy: ```python def greeks_key(instrument_id: InstrumentId): @@ -219,7 +235,8 @@ def greeks_from_cache(self, instrument_id: InstrumentId): ### Writing and reading data using a catalog -For streaming custom data to feather files or writing it to parquet files in a catalog (`register_arrow` needs to be used): +For streaming custom data to feather files or writing it to parquet files in a catalog +(`register_arrow` needs to be used): ```python register_arrow(GreeksData, GreeksData.schema(), GreeksData.to_catalog, GreeksData.from_catalog) @@ -261,11 +278,12 @@ GreeksTestData( ### Custom data type stub To enhance development convenience and improve code suggestions in your IDE, you can create a `.pyi` -stub file with the proper constructor signature for your custom data types. This is particularly -useful when the constructor is dynamically generated at runtime, as it allows the IDE to recognize +stub file with the proper constructor signature for your custom data types as well as type hints for attributes. +This is particularly useful when the constructor is dynamically generated at runtime, as it allows the IDE to recognize and provide suggestions for the class's methods and attributes. -For instance, if you have a custom data class defined in `greeks.py`, you can create a corresponding `greeks.pyi` file with the following constructor signature: +For instance, if you have a custom data class defined in `greeks.py`, you can create a corresponding `greeks.pyi` file +with the following constructor signature: ```python from nautilus_trader.core.data import Data @@ -273,12 +291,14 @@ from nautilus_trader.model.identifiers import InstrumentId class GreeksData(Data): + instrument_id: InstrumentId + delta: float + def __init__( self, ts_event: int = 0, ts_init: int = 0, instrument_id: InstrumentId = InstrumentId.from_str("ES.GLBX"), delta: float = 0.0, - ) -> GreeksData: - ... + ) -> GreeksData: ... ``` From 6deb9da725b76ae55e5614fe13f7a23ebdff4c65 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sat, 17 Aug 2024 20:02:08 +1000 Subject: [PATCH 52/72] Standardize double quotes black formatting --- docs/concepts/advanced/custom_data.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/concepts/advanced/custom_data.md b/docs/concepts/advanced/custom_data.md index 8c33e39af113..1c14427734bb 100644 --- a/docs/concepts/advanced/custom_data.md +++ b/docs/concepts/advanced/custom_data.md @@ -108,12 +108,11 @@ A signal is an automatically generated custom data identified by a name containi (str, float, int, bool or bytes). ```python -self.publish_signal('signal_name', value, ts_event) - -self.subscribe_signal('signal_name') +self.publish_signal("signal_name", value, ts_event) +self.subscribe_signal("signal_name") def on_data(self, data): - if data.is_signal('signal_name'): + if data.is_signal("signal_name"): print("Signal", data) ``` From 87e4a6a9a8665c97d133eaa2c47c0ba1f5bb90bb Mon Sep 17 00:00:00 2001 From: DevRoss Date: Sun, 18 Aug 2024 06:01:41 +0800 Subject: [PATCH 53/72] Add Binance Futures HEDGE mode (#1846) --- ...ce_futures_testnet_ema_cross_hedge_mode.py | 113 +++++ .../adapters/binance/common/enums.py | 27 ++ .../adapters/binance/common/execution.py | 102 ++++- .../adapters/binance/futures/enums.py | 11 - .../adapters/binance/futures/execution.py | 14 + .../binance/futures/schemas/account.py | 2 +- .../adapters/binance/futures/schemas/user.py | 2 +- .../adapters/binance/http/account.py | 7 + .../adapters/binance/spot/execution.py | 4 + .../strategies/ema_cross_hedge_mode.py | 362 +++++++++++++++ nautilus_trader/test_kit/stubs/identifiers.py | 12 + ...tp_futures_account_position_side_dual.json | 3 + .../binance/test_execution_futures.py | 422 +++++++++++++----- .../adapters/binance/test_http_account.py | 24 + .../adapters/binance/test_parsing_common.py | 63 ++- 15 files changed, 1009 insertions(+), 159 deletions(-) create mode 100644 examples/live/binance/binance_futures_testnet_ema_cross_hedge_mode.py create mode 100644 nautilus_trader/examples/strategies/ema_cross_hedge_mode.py create mode 100644 tests/integration_tests/adapters/binance/resources/http_responses/http_futures_account_position_side_dual.json diff --git a/examples/live/binance/binance_futures_testnet_ema_cross_hedge_mode.py b/examples/live/binance/binance_futures_testnet_ema_cross_hedge_mode.py new file mode 100644 index 000000000000..0eaf76d79c41 --- /dev/null +++ b/examples/live/binance/binance_futures_testnet_ema_cross_hedge_mode.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# ------------------------------------------------------------------------------------------------- +# Copyright (C) 2015-2024 Nautech Systems Pty Ltd. All rights reserved. +# https://nautechsystems.io +# +# Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------------------------------------------- + +from decimal import Decimal + +from nautilus_trader.adapters.binance.common.enums import BinanceAccountType +from nautilus_trader.adapters.binance.config import BinanceDataClientConfig +from nautilus_trader.adapters.binance.config import BinanceExecClientConfig +from nautilus_trader.adapters.binance.factories import BinanceLiveDataClientFactory +from nautilus_trader.adapters.binance.factories import BinanceLiveExecClientFactory +from nautilus_trader.config import InstrumentProviderConfig +from nautilus_trader.config import LiveExecEngineConfig +from nautilus_trader.config import LoggingConfig +from nautilus_trader.config import TradingNodeConfig +from nautilus_trader.examples.strategies.ema_cross import EMACross +from nautilus_trader.examples.strategies.ema_cross import EMACrossConfig +from nautilus_trader.live.node import TradingNode +from nautilus_trader.model.data import BarType +from nautilus_trader.model.identifiers import InstrumentId +from nautilus_trader.model.identifiers import TraderId + + +# *** THIS IS A TEST STRATEGY WITH NO ALPHA ADVANTAGE WHATSOEVER. *** +# *** IT IS NOT INTENDED TO BE USED TO TRADE LIVE WITH REAL MONEY. *** + + +# Configure the trading node +config_node = TradingNodeConfig( + trader_id=TraderId("TESTER-001"), + logging=LoggingConfig(log_level="INFO"), + exec_engine=LiveExecEngineConfig( + reconciliation=True, + reconciliation_lookback_mins=1440, + ), + data_clients={ + "BINANCE": BinanceDataClientConfig( + api_key=None, # 'BINANCE_API_KEY' env var + api_secret=None, # 'BINANCE_API_SECRET' env var + account_type=BinanceAccountType.USDT_FUTURE, + base_url_http=None, # Override with custom endpoint + base_url_ws=None, # Override with custom endpoint + us=False, # If client is for Binance US + testnet=True, # If client uses the testnet + instrument_provider=InstrumentProviderConfig(load_all=True), + ), + }, + exec_clients={ + "BINANCE": BinanceExecClientConfig( + api_key=None, # 'BINANCE_API_KEY' env var + api_secret=None, # 'BINANCE_API_SECRET' env var + account_type=BinanceAccountType.USDT_FUTURE, + base_url_http=None, # Override with custom endpoint + base_url_ws=None, # Override with custom endpoint + us=False, # If client is for Binance US + testnet=True, # If client uses the testnet + instrument_provider=InstrumentProviderConfig(load_all=True), + use_position_ids=False, + use_reduce_only=False, # Must be disabled for Hedge mode + ), + }, + timeout_connection=30.0, + timeout_reconciliation=10.0, + timeout_portfolio=10.0, + timeout_disconnection=10.0, + timeout_post_stop=5.0, +) + +# Instantiate the node with a configuration +node = TradingNode(config=config_node) + +# Configure your strategy +strat_config = EMACrossConfig( + instrument_id=InstrumentId.from_str("ETHUSDT-PERP.BINANCE"), + external_order_claims=[InstrumentId.from_str("ETHUSDT-PERP.BINANCE")], + bar_type=BarType.from_str("ETHUSDT-PERP.BINANCE-1-MINUTE-LAST-EXTERNAL"), + fast_ema_period=10, + slow_ema_period=20, + trade_size=Decimal("0.010"), + order_id_tag="001", + oms_type="HEDGING", +) + +# Instantiate your strategy +strategy = EMACross(config=strat_config) + +# Add your strategies and modules +node.trader.add_strategy(strategy) + +# Register your client factories with the node (can take user-defined factories) +node.add_data_client_factory("BINANCE", BinanceLiveDataClientFactory) +node.add_exec_client_factory("BINANCE", BinanceLiveExecClientFactory) +node.build() + + +# Stop and dispose of the node with SIGINT/CTRL+C +if __name__ == "__main__": + try: + node.run() + finally: + node.dispose() diff --git a/nautilus_trader/adapters/binance/common/enums.py b/nautilus_trader/adapters/binance/common/enums.py index e8d1c0530383..385689a7b7e0 100644 --- a/nautilus_trader/adapters/binance/common/enums.py +++ b/nautilus_trader/adapters/binance/common/enums.py @@ -35,9 +35,21 @@ from nautilus_trader.model.enums import TimeInForce from nautilus_trader.model.enums import TriggerType from nautilus_trader.model.enums import bar_aggregation_to_str +from nautilus_trader.model.identifiers import PositionId from nautilus_trader.model.orders import Order +@unique +class BinanceFuturesPositionSide(Enum): + """ + Represents a `Binance Futures` position side. + """ + + BOTH = "BOTH" + LONG = "LONG" + SHORT = "SHORT" + + @unique class BinanceRateLimitType(Enum): """ @@ -596,3 +608,18 @@ def parse_binance_trigger_type(self, trigger_type: str) -> TriggerType: raise NotImplementedError( # pragma: no cover (design-time error) "Cannot parse binance trigger type (not implemented).", # pragma: no cover ) + + def parse_position_id_to_binance_futures_position_side( + self, + position_id: PositionId, + ) -> BinanceFuturesPositionSide: + if position_id.value.endswith("LONG"): # Position Long + return BinanceFuturesPositionSide.LONG + elif position_id.value.endswith("SHORT"): # Position Short + return BinanceFuturesPositionSide.SHORT + elif position_id.value.endswith("BOTH"): + return BinanceFuturesPositionSide.BOTH + else: + raise RuntimeError( # pragma: no cover (design-time error) + f"unrecognized position id, was {position_id}", # pragma: no cover + ) diff --git a/nautilus_trader/adapters/binance/common/execution.py b/nautilus_trader/adapters/binance/common/execution.py index 7166c40c728c..09d77a018afa 100644 --- a/nautilus_trader/adapters/binance/common/execution.py +++ b/nautilus_trader/adapters/binance/common/execution.py @@ -24,6 +24,7 @@ from nautilus_trader.adapters.binance.common.enums import BinanceAccountType from nautilus_trader.adapters.binance.common.enums import BinanceEnumParser from nautilus_trader.adapters.binance.common.enums import BinanceErrorCode +from nautilus_trader.adapters.binance.common.enums import BinanceFuturesPositionSide from nautilus_trader.adapters.binance.common.enums import BinanceTimeInForce from nautilus_trader.adapters.binance.common.schemas.account import BinanceOrder from nautilus_trader.adapters.binance.common.schemas.account import BinanceUserTrade @@ -69,6 +70,7 @@ from nautilus_trader.model.identifiers import ClientId from nautilus_trader.model.identifiers import ClientOrderId from nautilus_trader.model.identifiers import InstrumentId +from nautilus_trader.model.identifiers import PositionId from nautilus_trader.model.identifiers import Symbol from nautilus_trader.model.identifiers import Venue from nautilus_trader.model.identifiers import VenueOrderId @@ -170,6 +172,7 @@ def __init__( self._log.info(f"{config.max_retries=}", LogColor.BLUE) self._log.info(f"{config.retry_delay=}", LogColor.BLUE) + self._is_dual_side_position: bool | None = None # Initialized on connection self._set_account_id( AccountId(f"{name or BINANCE_VENUE.value}-{self._binance_account_type.value}-master"), ) @@ -263,6 +266,9 @@ async def _connect(self) -> None: # Authenticate API key and update account(s) await self._update_account_state() + # Set dual side position + await self._init_dual_side_position() + # Get listen keys response: BinanceListenKey = await self._http_user.create_listen_key() except BinanceError as e: @@ -288,6 +294,10 @@ async def _update_account_state(self) -> None: # Replace method in child class raise NotImplementedError + async def _init_dual_side_position(self) -> None: + # Replace method in child class + raise NotImplementedError + async def _ping_listen_keys(self) -> None: try: while True: @@ -618,21 +628,65 @@ def _determine_reduce_only(self, order: Order) -> bool: return order.is_reduce_only if self._use_reduce_only else False def _determine_reduce_only_str(self, order: Order) -> str | None: - if self._binance_account_type.is_futures: + # `reduceOnly` Cannot be sent in Futures Hedge Mode + if self._binance_account_type.is_futures and not self._is_dual_side_position: return str(self._determine_reduce_only(order)) return None + def _get_position_side_from_position_id( + self, + position_id: PositionId | None, + ) -> BinanceFuturesPositionSide | None: + """ + Parse the position side from the position ID. + + Parameters + ---------- + position_id : PositionId | None + The position ID, can be `None`. + Position ID must end with either 'LONG' or 'SHORT' for Binance Futures Hedge position mode. + + Returns + ------- + BinanceFuturesPositionSide | None + + """ + position_side = None + if self._binance_account_type.is_spot_or_margin: # Spot or Margin mode + return position_side + elif not self._is_dual_side_position: # One-way position mode + return BinanceFuturesPositionSide.BOTH + + # For Binance Futures Hedge mode, the position side must be specified in the position_id + PyCondition.not_none(position_id, "position_id") + position_side = self._enum_parser.parse_position_id_to_binance_futures_position_side( + position_id, + ) + # Check if the position side is valid + PyCondition.is_in( + position_side, + [BinanceFuturesPositionSide.LONG, BinanceFuturesPositionSide.SHORT], + "position_side", + "HedgeModePositionSides", + ) + return position_side + async def _submit_order(self, command: SubmitOrder) -> None: - await self._submit_order_inner(command.order) + position_side = self._get_position_side_from_position_id(command.position_id) + await self._submit_order_inner(command.order, position_side) - async def _submit_order_inner(self, order: Order) -> None: + async def _submit_order_inner( + self, + order: Order, + position_side: BinanceFuturesPositionSide | None, + ) -> None: if order.is_closed: self._log.warning(f"Cannot submit already closed order {order}") return # Check validity self._check_order_validity(order) - self._log.debug(f"Submitting {order}") + self._log.debug(f"Submitting {order}, position_side={position_side}") # Generate event here to ensure correct ordering of events self.generate_order_submitted( @@ -644,7 +698,7 @@ async def _submit_order_inner(self, order: Order) -> None: while True: try: - await self._submit_order_method[order.order_type](order) + await self._submit_order_method[order.order_type](order, position_side) self._order_retries.pop(order.client_order_id, None) break # Successful request except KeyError: @@ -671,7 +725,11 @@ async def _submit_order_inner(self, order: Order) -> None: ) await asyncio.sleep(self._retry_delay) - async def _submit_market_order(self, order: MarketOrder) -> None: + async def _submit_market_order( + self, + order: MarketOrder, + position_side: BinanceFuturesPositionSide | None, + ) -> None: await self._http_account.new_order( symbol=order.instrument_id.symbol.value, side=self._enum_parser.parse_internal_order_side(order.side), @@ -680,9 +738,14 @@ async def _submit_market_order(self, order: MarketOrder) -> None: reduce_only=self._determine_reduce_only_str(order), new_client_order_id=order.client_order_id.value, recv_window=str(self._recv_window), + position_side=position_side, ) - async def _submit_limit_order(self, order: LimitOrder) -> None: + async def _submit_limit_order( + self, + order: LimitOrder, + position_side: BinanceFuturesPositionSide | None, + ) -> None: time_in_force = self._determine_time_in_force(order) if order.is_post_only and self._binance_account_type.is_spot_or_margin: time_in_force = None @@ -701,9 +764,14 @@ async def _submit_limit_order(self, order: LimitOrder) -> None: reduce_only=self._determine_reduce_only_str(order), new_client_order_id=order.client_order_id.value, recv_window=str(self._recv_window), + position_side=position_side, ) - async def _submit_stop_limit_order(self, order: StopLimitOrder) -> None: + async def _submit_stop_limit_order( + self, + order: StopLimitOrder, + position_side: BinanceFuturesPositionSide | None, + ) -> None: if self._binance_account_type.is_spot_or_margin: working_type = None elif order.trigger_type in (TriggerType.DEFAULT, TriggerType.LAST_TRADE): @@ -732,9 +800,11 @@ async def _submit_stop_limit_order(self, order: StopLimitOrder) -> None: reduce_only=self._determine_reduce_only_str(order), new_client_order_id=order.client_order_id.value, recv_window=str(self._recv_window), + position_side=position_side, ) async def _submit_order_list(self, command: SubmitOrderList) -> None: + position_side = self._get_position_side_from_position_id(command.position_id) for order in command.order_list.orders: self.generate_order_submitted( strategy_id=order.strategy_id, @@ -746,9 +816,13 @@ async def _submit_order_list(self, command: SubmitOrderList) -> None: for order in command.order_list.orders: if order.linked_order_ids: # TODO: Implement self._log.warning(f"Cannot yet handle OCO conditional orders, {order}") - await self._submit_order_inner(order) + await self._submit_order_inner(order, position_side) - async def _submit_stop_market_order(self, order: StopMarketOrder) -> None: + async def _submit_stop_market_order( + self, + order: StopMarketOrder, + position_side: BinanceFuturesPositionSide | None, + ) -> None: if self._binance_account_type.is_spot_or_margin: working_type = None elif order.trigger_type in (TriggerType.DEFAULT, TriggerType.LAST_TRADE): @@ -775,9 +849,14 @@ async def _submit_stop_market_order(self, order: StopMarketOrder) -> None: reduce_only=self._determine_reduce_only_str(order), new_client_order_id=order.client_order_id.value, recv_window=str(self._recv_window), + position_side=position_side, ) - async def _submit_trailing_stop_market_order(self, order: TrailingStopMarketOrder) -> None: + async def _submit_trailing_stop_market_order( + self, + order: TrailingStopMarketOrder, + position_side: BinanceFuturesPositionSide | None, + ) -> None: if order.trigger_type in (TriggerType.DEFAULT, TriggerType.LAST_TRADE): working_type = "CONTRACT_PRICE" elif order.trigger_type == TriggerType.MARK_PRICE: @@ -841,6 +920,7 @@ async def _submit_trailing_stop_market_order(self, order: TrailingStopMarketOrde reduce_only=self._determine_reduce_only_str(order), new_client_order_id=order.client_order_id.value, recv_window=str(self._recv_window), + position_side=position_side, ) def _get_cached_instrument_id(self, symbol: str) -> InstrumentId: diff --git a/nautilus_trader/adapters/binance/futures/enums.py b/nautilus_trader/adapters/binance/futures/enums.py index a28a2eaa48e1..2e33ebba7e4f 100644 --- a/nautilus_trader/adapters/binance/futures/enums.py +++ b/nautilus_trader/adapters/binance/futures/enums.py @@ -66,17 +66,6 @@ class BinanceFuturesContractStatus(Enum): CLOSE = "CLOSE" -@unique -class BinanceFuturesPositionSide(Enum): - """ - Represents a `Binance Futures` position side. - """ - - BOTH = "BOTH" - LONG = "LONG" - SHORT = "SHORT" - - @unique class BinanceFuturesWorkingType(Enum): """ diff --git a/nautilus_trader/adapters/binance/futures/execution.py b/nautilus_trader/adapters/binance/futures/execution.py index 35aecf08490b..e8bb4e789f7b 100644 --- a/nautilus_trader/adapters/binance/futures/execution.py +++ b/nautilus_trader/adapters/binance/futures/execution.py @@ -30,6 +30,7 @@ from nautilus_trader.adapters.binance.futures.http.user import BinanceFuturesUserDataHttpAPI from nautilus_trader.adapters.binance.futures.providers import BinanceFuturesInstrumentProvider from nautilus_trader.adapters.binance.futures.schemas.account import BinanceFuturesAccountInfo +from nautilus_trader.adapters.binance.futures.schemas.account import BinanceFuturesDualSidePosition from nautilus_trader.adapters.binance.futures.schemas.account import BinanceFuturesPositionRisk from nautilus_trader.adapters.binance.futures.schemas.user import BinanceFuturesAccountUpdateWrapper from nautilus_trader.adapters.binance.futures.schemas.user import BinanceFuturesOrderUpdateWrapper @@ -169,6 +170,19 @@ async def _update_account_state(self) -> None: account.set_leverage(instrument_id, leverage) self._log.debug(f"Set leverage {position.symbol} {leverage}X") + async def _init_dual_side_position(self) -> None: + binance_futures_dual_side_position: BinanceFuturesDualSidePosition = ( + await self._futures_http_account.query_futures_hedge_mode() + ) + # "true": Hedge Mode; "false": One-way Mode + self._is_dual_side_position = binance_futures_dual_side_position.dualSidePosition + if self._is_dual_side_position: + PyCondition.false( + self._use_reduce_only, + "Cannot use `reduce_only` with Binance Hedge Mode", + ) + self._log.info(f"Dual side position: {self._is_dual_side_position}") + # -- EXECUTION REPORTS ------------------------------------------------------------------------ async def _get_binance_position_status_reports( diff --git a/nautilus_trader/adapters/binance/futures/schemas/account.py b/nautilus_trader/adapters/binance/futures/schemas/account.py index 8161a3d55f13..777a264b0f58 100644 --- a/nautilus_trader/adapters/binance/futures/schemas/account.py +++ b/nautilus_trader/adapters/binance/futures/schemas/account.py @@ -17,8 +17,8 @@ import msgspec +from nautilus_trader.adapters.binance.common.enums import BinanceFuturesPositionSide from nautilus_trader.adapters.binance.futures.enums import BinanceFuturesEnumParser -from nautilus_trader.adapters.binance.futures.enums import BinanceFuturesPositionSide from nautilus_trader.core.uuid import UUID4 from nautilus_trader.execution.reports import PositionStatusReport from nautilus_trader.model.identifiers import AccountId diff --git a/nautilus_trader/adapters/binance/futures/schemas/user.py b/nautilus_trader/adapters/binance/futures/schemas/user.py index d6beaf152ba2..822986a6116d 100644 --- a/nautilus_trader/adapters/binance/futures/schemas/user.py +++ b/nautilus_trader/adapters/binance/futures/schemas/user.py @@ -19,13 +19,13 @@ from nautilus_trader.adapters.binance.common.enums import BinanceEnumParser from nautilus_trader.adapters.binance.common.enums import BinanceExecutionType +from nautilus_trader.adapters.binance.common.enums import BinanceFuturesPositionSide from nautilus_trader.adapters.binance.common.enums import BinanceOrderSide from nautilus_trader.adapters.binance.common.enums import BinanceOrderStatus from nautilus_trader.adapters.binance.common.enums import BinanceOrderType from nautilus_trader.adapters.binance.common.enums import BinanceTimeInForce from nautilus_trader.adapters.binance.common.execution import BinanceCommonExecutionClient from nautilus_trader.adapters.binance.futures.enums import BinanceFuturesEventType -from nautilus_trader.adapters.binance.futures.enums import BinanceFuturesPositionSide from nautilus_trader.adapters.binance.futures.enums import BinanceFuturesPositionUpdateReason from nautilus_trader.adapters.binance.futures.enums import BinanceFuturesWorkingType from nautilus_trader.core.datetime import millis_to_nanos diff --git a/nautilus_trader/adapters/binance/http/account.py b/nautilus_trader/adapters/binance/http/account.py index 794fec889a8e..a1783e0710f3 100644 --- a/nautilus_trader/adapters/binance/http/account.py +++ b/nautilus_trader/adapters/binance/http/account.py @@ -17,6 +17,7 @@ import msgspec from nautilus_trader.adapters.binance.common.enums import BinanceAccountType +from nautilus_trader.adapters.binance.common.enums import BinanceFuturesPositionSide from nautilus_trader.adapters.binance.common.enums import BinanceNewOrderRespType from nautilus_trader.adapters.binance.common.enums import BinanceOrderSide from nautilus_trader.adapters.binance.common.enums import BinanceOrderType @@ -137,6 +138,9 @@ class PostParameters(msgspec.Struct, omit_defaults=True, frozen=True): timeInForce : BinanceTimeInForce, optional Mandatory for LIMIT, STOP_LOSS_LIMIT, TAKE_PROFIT_LIMIT orders. The time in force of the order (GTC, IOC..) + positionSide : BinanceFuturesPositionSide, optional + Only for FUTURES orders. + Must be sent in Hedge Mode and the position side must be one of LONG and SHORT. quantity : str, optional Mandatory for all order types, except STOP_MARKET/TAKE_PROFIT_MARKET and TRAILING_STOP_MARKET orders @@ -215,6 +219,7 @@ class PostParameters(msgspec.Struct, omit_defaults=True, frozen=True): side: BinanceOrderSide type: BinanceOrderType timeInForce: BinanceTimeInForce | None = None + positionSide: BinanceFuturesPositionSide | None = None quantity: str | None = None quoteOrderQty: str | None = None price: str | None = None @@ -615,6 +620,7 @@ async def new_order( side: BinanceOrderSide, order_type: BinanceOrderType, time_in_force: BinanceTimeInForce | None = None, + position_side: BinanceFuturesPositionSide | None = None, quantity: str | None = None, quote_order_qty: str | None = None, price: str | None = None, @@ -644,6 +650,7 @@ async def new_order( side=side, type=order_type, timeInForce=time_in_force, + positionSide=position_side, quantity=quantity, quoteOrderQty=quote_order_qty, price=price, diff --git a/nautilus_trader/adapters/binance/spot/execution.py b/nautilus_trader/adapters/binance/spot/execution.py index 467bfa640d16..7d5ca0cf4097 100644 --- a/nautilus_trader/adapters/binance/spot/execution.py +++ b/nautilus_trader/adapters/binance/spot/execution.py @@ -155,6 +155,10 @@ async def _update_account_state(self) -> None: while self.get_account() is None: await asyncio.sleep(0.1) + async def _init_dual_side_position(self) -> None: + self._is_dual_side_position = False + self._log.info(f"Dual side position: {self._is_dual_side_position}") + # -- EXECUTION REPORTS ------------------------------------------------------------------------ async def _get_binance_position_status_reports( diff --git a/nautilus_trader/examples/strategies/ema_cross_hedge_mode.py b/nautilus_trader/examples/strategies/ema_cross_hedge_mode.py new file mode 100644 index 000000000000..9110208a6836 --- /dev/null +++ b/nautilus_trader/examples/strategies/ema_cross_hedge_mode.py @@ -0,0 +1,362 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (C) 2015-2024 Nautech Systems Pty Ltd. All rights reserved. +# https://nautechsystems.io +# +# Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------------------------------------------- + +from decimal import Decimal + +import pandas as pd + +from nautilus_trader.common.enums import LogColor +from nautilus_trader.config import PositiveInt +from nautilus_trader.config import StrategyConfig +from nautilus_trader.core.correctness import PyCondition +from nautilus_trader.core.data import Data +from nautilus_trader.core.message import Event +from nautilus_trader.indicators.average.ema import ExponentialMovingAverage +from nautilus_trader.model.book import OrderBook +from nautilus_trader.model.data import Bar +from nautilus_trader.model.data import BarType +from nautilus_trader.model.data import OrderBookDeltas +from nautilus_trader.model.data import QuoteTick +from nautilus_trader.model.data import TradeTick +from nautilus_trader.model.enums import OrderSide +from nautilus_trader.model.identifiers import InstrumentId +from nautilus_trader.model.identifiers import PositionId +from nautilus_trader.model.instruments import Instrument +from nautilus_trader.model.orders import MarketOrder +from nautilus_trader.trading.strategy import Strategy + + +# *** THIS IS A TEST STRATEGY WITH NO ALPHA ADVANTAGE WHATSOEVER. *** +# *** IT IS NOT INTENDED TO BE USED TO TRADE LIVE WITH REAL MONEY. *** + + +class EMACrossConfig(StrategyConfig, frozen=True): + """ + Configuration for ``EMACross`` instances. + + Parameters + ---------- + instrument_id : InstrumentId + The instrument ID for the strategy. + bar_type : BarType + The bar type for the strategy. + trade_size : Decimal + The position size per trade. + fast_ema_period : int, default 10 + The fast EMA period. + slow_ema_period : int, default 20 + The slow EMA period. + subscribe_trade_ticks : bool, default True + If trade ticks should be subscribed to. + subscribe_quote_ticks : bool, default False + If quote ticks should be subscribed to. + close_positions_on_stop : bool, default True + If all open positions should be closed on strategy stop. + + """ + + instrument_id: InstrumentId + bar_type: BarType + trade_size: Decimal + fast_ema_period: PositiveInt = 10 + slow_ema_period: PositiveInt = 20 + subscribe_trade_ticks: bool = True + subscribe_quote_ticks: bool = False + close_positions_on_stop: bool = True + + +class EMACross(Strategy): + """ + A simple moving average cross example strategy. + + When the fast EMA crosses the slow EMA then enter a position at the market + in that direction. + + Parameters + ---------- + config : EMACrossConfig + The configuration for the instance. + + Raises + ------ + ValueError + If `config.fast_ema_period` is not less than `config.slow_ema_period`. + + """ + + def __init__(self, config: EMACrossConfig) -> None: + PyCondition.true( + config.fast_ema_period < config.slow_ema_period, + "{config.fast_ema_period=} must be less than {config.slow_ema_period=}", + ) + super().__init__(config) + + # Configuration + self.instrument_id = config.instrument_id + self.bar_type = config.bar_type + self.trade_size = config.trade_size + + # Create the indicators for the strategy + self.fast_ema = ExponentialMovingAverage(config.fast_ema_period) + self.slow_ema = ExponentialMovingAverage(config.slow_ema_period) + + self.close_positions_on_stop = config.close_positions_on_stop + self.instrument: Instrument = None + + def on_start(self) -> None: + """ + Actions to be performed on strategy start. + """ + self.instrument = self.cache.instrument(self.instrument_id) + if self.instrument is None: + self.log.error(f"Could not find instrument for {self.instrument_id}") + self.stop() + return + + # Register the indicators for updating + self.register_indicator_for_bars(self.bar_type, self.fast_ema) + self.register_indicator_for_bars(self.bar_type, self.slow_ema) + + # Get historical data + self.request_bars(self.bar_type, start=self._clock.utc_now() - pd.Timedelta(days=1)) + # self.request_quote_ticks(self.instrument_id) + # self.request_trade_ticks(self.instrument_id) + + # Subscribe to live data + self.subscribe_bars(self.bar_type) + + if self.config.subscribe_quote_ticks: + self.subscribe_quote_ticks(self.instrument_id) + if self.config.subscribe_trade_ticks: + self.subscribe_trade_ticks(self.instrument_id) + + # self.subscribe_ticker(self.instrument_id) # For debugging + # self.subscribe_order_book_deltas(self.instrument_id, depth=20) # For debugging + # self.subscribe_order_book_at_interval(self.instrument_id, depth=20) # For debugging + + def on_instrument(self, instrument: Instrument) -> None: + """ + Actions to be performed when the strategy is running and receives an instrument. + + Parameters + ---------- + instrument : Instrument + The instrument received. + + """ + # For debugging (must add a subscription) + # self.log.info(repr(instrument), LogColor.CYAN) + + def on_order_book_deltas(self, deltas: OrderBookDeltas) -> None: + """ + Actions to be performed when the strategy is running and receives order book + deltas. + + Parameters + ---------- + deltas : OrderBookDeltas + The order book deltas received. + + """ + # For debugging (must add a subscription) + # self.log.info(repr(deltas), LogColor.CYAN) + + def on_order_book(self, order_book: OrderBook) -> None: + """ + Actions to be performed when the strategy is running and receives an order book. + + Parameters + ---------- + order_book : OrderBook + The order book received. + + """ + # For debugging (must add a subscription) + # self.log.info(repr(order_book), LogColor.CYAN) + + def on_quote_tick(self, tick: QuoteTick) -> None: + """ + Actions to be performed when the strategy is running and receives a quote tick. + + Parameters + ---------- + tick : QuoteTick + The tick received. + + """ + # For debugging (must add a subscription) + self.log.info(repr(tick), LogColor.CYAN) + + def on_trade_tick(self, tick: TradeTick) -> None: + """ + Actions to be performed when the strategy is running and receives a trade tick. + + Parameters + ---------- + tick : TradeTick + The tick received. + + """ + # For debugging (must add a subscription) + self.log.info(repr(tick), LogColor.CYAN) + + def on_bar(self, bar: Bar) -> None: + """ + Actions to be performed when the strategy is running and receives a bar. + + Parameters + ---------- + bar : Bar + The bar received. + + """ + self.log.info(repr(bar), LogColor.CYAN) + + # Check if indicators ready + if not self.indicators_initialized(): + self.log.info( + f"Waiting for indicators to warm up [{self.cache.bar_count(self.bar_type)}]", + color=LogColor.BLUE, + ) + return # Wait for indicators to warm up... + + if bar.is_single_price(): + # Implies no market information for this bar + return + + # BUY LOGIC + if self.fast_ema.value >= self.slow_ema.value: + if self.portfolio.is_flat(self.instrument_id): + self.buy() + elif self.portfolio.is_net_short(self.instrument_id): + self.close_all_positions(self.instrument_id) + self.buy() + # SELL LOGIC + elif self.fast_ema.value < self.slow_ema.value: + if self.portfolio.is_flat(self.instrument_id): + self.sell() + elif self.portfolio.is_net_long(self.instrument_id): + self.close_all_positions(self.instrument_id) + self.sell() + + def buy(self) -> None: + """ + Users simple buy method (example). + """ + order: MarketOrder = self.order_factory.market( + instrument_id=self.instrument_id, + order_side=OrderSide.BUY, + quantity=self.instrument.make_qty(self.trade_size), + # time_in_force=TimeInForce.FOK, + ) + + # LONG suffix is recognized as a long position by Binance adapter. + position_id = PositionId(f"{self.instrument_id}-LONG") + self.submit_order(order, position_id) + + def sell(self) -> None: + """ + Users simple sell method (example). + """ + order: MarketOrder = self.order_factory.market( + instrument_id=self.instrument_id, + order_side=OrderSide.SELL, + quantity=self.instrument.make_qty(self.trade_size), + # time_in_force=TimeInForce.FOK, + ) + # SHORT suffix is recognized as a short position by Binance adapter. + position_id = PositionId(f"{self.instrument_id}-SHORT") + self.submit_order(order, position_id) + + def on_data(self, data: Data) -> None: + """ + Actions to be performed when the strategy is running and receives data. + + Parameters + ---------- + data : Data + The data received. + + """ + + def on_event(self, event: Event) -> None: + """ + Actions to be performed when the strategy is running and receives an event. + + Parameters + ---------- + event : Event + The event received. + + """ + + def on_stop(self) -> None: + """ + Actions to be performed when the strategy is stopped. + """ + self.cancel_all_orders(self.instrument_id) + if self.close_positions_on_stop: + self.close_all_positions(self.instrument_id) + + # Unsubscribe from data + self.unsubscribe_bars(self.bar_type) + # self.unsubscribe_quote_ticks(self.instrument_id) + self.unsubscribe_trade_ticks(self.instrument_id) + # self.unsubscribe_ticker(self.instrument_id) + # self.unsubscribe_order_book_deltas(self.instrument_id) + # self.unsubscribe_order_book_at_interval(self.instrument_id) + + def on_reset(self) -> None: + """ + Actions to be performed when the strategy is reset. + """ + # Reset indicators here + self.fast_ema.reset() + self.slow_ema.reset() + + def on_save(self) -> dict[str, bytes]: + """ + Actions to be performed when the strategy is saved. + + Create and return a state dictionary of values to be saved. + + Returns + ------- + dict[str, bytes] + The strategy state dictionary. + + """ + return {} + + def on_load(self, state: dict[str, bytes]) -> None: + """ + Actions to be performed when the strategy is loaded. + + Saved state values will be contained in the give state dictionary. + + Parameters + ---------- + state : dict[str, bytes] + The strategy state dictionary. + + """ + + def on_dispose(self) -> None: + """ + Actions to be performed when the strategy is disposed. + + Cleanup any resources used by the strategy here. + + """ diff --git a/nautilus_trader/test_kit/stubs/identifiers.py b/nautilus_trader/test_kit/stubs/identifiers.py index 81c6e8c52313..3075dd3053f6 100644 --- a/nautilus_trader/test_kit/stubs/identifiers.py +++ b/nautilus_trader/test_kit/stubs/identifiers.py @@ -113,3 +113,15 @@ def venue_order_id() -> VenueOrderId: @staticmethod def trade_id() -> TradeId: return TradeId("1") + + @staticmethod + def position_id_long() -> PositionId: + return PositionId("001-LONG") + + @staticmethod + def position_id_short() -> PositionId: + return PositionId("001-SHORT") + + @staticmethod + def position_id_both() -> PositionId: + return PositionId("001-BOTH") diff --git a/tests/integration_tests/adapters/binance/resources/http_responses/http_futures_account_position_side_dual.json b/tests/integration_tests/adapters/binance/resources/http_responses/http_futures_account_position_side_dual.json new file mode 100644 index 000000000000..689b0d5f287d --- /dev/null +++ b/tests/integration_tests/adapters/binance/resources/http_responses/http_futures_account_position_side_dual.json @@ -0,0 +1,3 @@ +{ + "dualSidePosition": true +} \ No newline at end of file diff --git a/tests/integration_tests/adapters/binance/test_execution_futures.py b/tests/integration_tests/adapters/binance/test_execution_futures.py index 53f168e4b3ea..58f2a8ee3ea1 100644 --- a/tests/integration_tests/adapters/binance/test_execution_futures.py +++ b/tests/integration_tests/adapters/binance/test_execution_futures.py @@ -20,12 +20,14 @@ from nautilus_trader.adapters.binance.common.constants import BINANCE_VENUE from nautilus_trader.adapters.binance.common.enums import BinanceAccountType +from nautilus_trader.adapters.binance.config import BinanceExecClientConfig from nautilus_trader.adapters.binance.futures.execution import BinanceFuturesExecutionClient from nautilus_trader.adapters.binance.futures.providers import BinanceFuturesInstrumentProvider from nautilus_trader.adapters.binance.http.client import BinanceHttpClient from nautilus_trader.common.component import LiveClock from nautilus_trader.common.component import MessageBus from nautilus_trader.config import InstrumentProviderConfig +from nautilus_trader.core.nautilus_pyo3 import HttpMethod from nautilus_trader.core.uuid import UUID4 from nautilus_trader.data.engine import DataEngine from nautilus_trader.execution.engine import ExecutionEngine @@ -47,7 +49,6 @@ ETHUSDT_PERP_BINANCE = TestInstrumentProvider.ethusdt_perp_binance() -@pytest.mark.skip(reason="WIP") class TestBinanceFuturesExecutionClient: def setup(self): # Fixture Setup @@ -112,6 +113,8 @@ def setup(self): cache=self.cache, clock=self.clock, instrument_provider=self.provider, + base_url_ws="", + config=BinanceExecClientConfig(), account_type=BinanceAccountType.USDT_FUTURE, ) @@ -125,11 +128,29 @@ def setup(self): ) @pytest.mark.asyncio() - async def test_submit_market_order(self, mocker): + @pytest.mark.parametrize( + ("_is_dual_side_position", "position_id", "expected"), + [ + # One-way mode + (False, None, "BOTH"), + (False, TestIdStubs.position_id(), "BOTH"), + (False, TestIdStubs.position_id_long(), "BOTH"), + (False, TestIdStubs.position_id_short(), "BOTH"), + (False, TestIdStubs.position_id_both(), "BOTH"), + # Hedge mode + (True, TestIdStubs.position_id_long(), "LONG"), + (True, TestIdStubs.position_id_short(), "SHORT"), + # (True, TestIdStubs.position_id_both(), "BOTH"), + ], + ) + async def test_submit_market_order(self, mocker, _is_dual_side_position, position_id, expected): # Arrange mock_send_request = mocker.patch( target="nautilus_trader.adapters.binance.http.client.BinanceHttpClient.send_request", ) + # For one-way mode: _is_dual_side_position is False + # For hedge mode: _is_dual_side_position is True + mocker.patch.object(self.exec_client, "_is_dual_side_position", _is_dual_side_position) order = self.strategy.order_factory.market( instrument_id=ETHUSDT_PERP_BINANCE.id, @@ -137,37 +158,53 @@ async def test_submit_market_order(self, mocker): quantity=Quantity.from_int(1), ) self.cache.add_order(order, None) - submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=self.strategy.id, - position_id=None, + position_id=position_id, order=order, command_id=UUID4(), ts_init=0, ) - + print(f"submit_order: {submit_order}") # Act self.exec_client.submit_order(submit_order) await asyncio.sleep(0.3) # Assert - request = mock_send_request.call_args[0] - assert request[0] == "POST" - assert request[1] == "/fapi/v1/order" - assert request[2]["symbol"] == "ETHUSDT" # -PERP was stripped - assert request[2]["type"] == "MARKET" - assert request[2]["side"] == "BUY" - assert request[2]["quantity"] == "1" - assert request[2]["newClientOrderId"] is not None - assert request[2]["recvWindow"] == "5000" + request = mock_send_request.call_args + assert request[0][0] == HttpMethod.POST + assert request[0][1] == "/fapi/v1/order" + assert request[1]["payload"]["symbol"] == "ETHUSDT" # -PERP was stripped + assert request[1]["payload"]["type"] == "MARKET" + assert request[1]["payload"]["side"] == "BUY" + assert request[1]["payload"]["quantity"] == "1" + assert request[1]["payload"]["newClientOrderId"] is not None + assert request[1]["payload"]["recvWindow"] == "5000" + assert request[1]["payload"]["positionSide"] == expected @pytest.mark.asyncio() - async def test_submit_limit_order(self, mocker): + @pytest.mark.parametrize( + ("_is_dual_side_position", "position_id", "expected"), + [ + # One-way mode + (False, None, "BOTH"), + (False, TestIdStubs.position_id(), "BOTH"), + (False, TestIdStubs.position_id_long(), "BOTH"), + (False, TestIdStubs.position_id_short(), "BOTH"), + (False, TestIdStubs.position_id_both(), "BOTH"), + # Hedge mode + (True, TestIdStubs.position_id_long(), "LONG"), + (True, TestIdStubs.position_id_short(), "SHORT"), + # (True, TestIdStubs.position_id_both(), "BOTH"), + ], + ) + async def test_submit_limit_order(self, mocker, _is_dual_side_position, position_id, expected): # Arrange mock_send_request = mocker.patch( target="nautilus_trader.adapters.binance.http.client.BinanceHttpClient.send_request", ) + mocker.patch.object(self.exec_client, "_is_dual_side_position", _is_dual_side_position) order = self.strategy.order_factory.limit( instrument_id=ETHUSDT_PERP_BINANCE.id, @@ -180,7 +217,7 @@ async def test_submit_limit_order(self, mocker): submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=self.strategy.id, - position_id=None, + position_id=position_id, order=order, command_id=UUID4(), ts_init=0, @@ -191,23 +228,46 @@ async def test_submit_limit_order(self, mocker): await asyncio.sleep(0.3) # Assert - request = mock_send_request.call_args[0] - assert request[0] == "POST" - assert request[1] == "/fapi/v1/order" - assert request[2]["symbol"] == "ETHUSDT" # -PERP was stripped - assert request[2]["side"] == "BUY" - assert request[2]["type"] == "LIMIT" - assert request[2]["quantity"] == "10" - assert request[2]["newClientOrderId"] is not None - assert request[2]["recvWindow"] == "5000" - assert request[2]["signature"] is not None + request = mock_send_request.call_args + assert request[0][0] == HttpMethod.POST + assert request[0][1] == "/fapi/v1/order" + assert request[1]["payload"]["symbol"] == "ETHUSDT" # -PERP was stripped + assert request[1]["payload"]["side"] == "BUY" + assert request[1]["payload"]["type"] == "LIMIT" + assert request[1]["payload"]["quantity"] == "10" + assert request[1]["payload"]["newClientOrderId"] is not None + assert request[1]["payload"]["recvWindow"] == "5000" + assert request[1]["payload"]["signature"] is not None + assert request[1]["payload"]["positionSide"] == expected @pytest.mark.asyncio() - async def test_submit_limit_post_only_order(self, mocker): + @pytest.mark.parametrize( + ("_is_dual_side_position", "position_id", "expected"), + [ + # One-way mode + (False, None, "BOTH"), + (False, TestIdStubs.position_id(), "BOTH"), + (False, TestIdStubs.position_id_long(), "BOTH"), + (False, TestIdStubs.position_id_short(), "BOTH"), + (False, TestIdStubs.position_id_both(), "BOTH"), + # Hedge mode + (True, TestIdStubs.position_id_long(), "LONG"), + (True, TestIdStubs.position_id_short(), "SHORT"), + # (True, TestIdStubs.position_id_both(), "BOTH"), + ], + ) + async def test_submit_limit_post_only_order( + self, + mocker, + _is_dual_side_position, + position_id, + expected, + ): # Arrange mock_send_request = mocker.patch( target="nautilus_trader.adapters.binance.http.client.BinanceHttpClient.send_request", ) + mocker.patch.object(self.exec_client, "_is_dual_side_position", _is_dual_side_position) order = self.strategy.order_factory.limit( instrument_id=ETHUSDT_PERP_BINANCE.id, @@ -221,7 +281,7 @@ async def test_submit_limit_post_only_order(self, mocker): submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=self.strategy.id, - position_id=None, + position_id=position_id, order=order, command_id=UUID4(), ts_init=0, @@ -232,25 +292,49 @@ async def test_submit_limit_post_only_order(self, mocker): await asyncio.sleep(0.3) # Assert - request = mock_send_request.call_args[0] - assert request[0] == "POST" - assert request[1] == "/fapi/v1/order" - assert request[2]["symbol"] == "ETHUSDT" # -PERP was stripped - assert request[2]["side"] == "BUY" - assert request[2]["type"] == "LIMIT" - assert request[2]["timeInForce"] == "GTX" - assert request[2]["quantity"] == "10" - assert request[2]["price"] == "10050.80" - assert request[2]["newClientOrderId"] is not None - assert request[2]["recvWindow"] == "5000" - assert request[2]["signature"] is not None + request = mock_send_request.call_args + assert request[0][0] == HttpMethod.POST + assert request[0][1] == "/fapi/v1/order" + assert request[1]["payload"]["symbol"] == "ETHUSDT" # -PERP was stripped + assert request[1]["payload"]["side"] == "BUY" + assert request[1]["payload"]["type"] == "LIMIT" + assert request[1]["payload"]["timeInForce"] == "GTX" + assert request[1]["payload"]["quantity"] == "10" + assert request[1]["payload"]["price"] == "10050.80" + assert request[1]["payload"]["newClientOrderId"] is not None + assert request[1]["payload"]["recvWindow"] == "5000" + assert request[1]["payload"]["signature"] is not None + assert request[1]["payload"]["positionSide"] == expected @pytest.mark.asyncio() - async def test_submit_stop_market_order(self, mocker): + @pytest.mark.parametrize( + ("_is_dual_side_position", "position_id", "expected"), + [ + # One-way mode + (False, None, "BOTH"), + (False, TestIdStubs.position_id(), "BOTH"), + (False, TestIdStubs.position_id_long(), "BOTH"), + (False, TestIdStubs.position_id_short(), "BOTH"), + (False, TestIdStubs.position_id_both(), "BOTH"), + # Hedge mode + (True, TestIdStubs.position_id_long(), "LONG"), + (True, TestIdStubs.position_id_short(), "SHORT"), + # (True, TestIdStubs.position_id_both(), "BOTH"), + ], + ) + async def test_submit_stop_market_order( + self, + mocker, + _is_dual_side_position, + position_id, + expected, + ): # Arrange mock_send_request = mocker.patch( target="nautilus_trader.adapters.binance.http.client.BinanceHttpClient.send_request", ) + mocker.patch.object(self.exec_client, "_is_dual_side_position", _is_dual_side_position) + mocker.patch.object(self.exec_client, "_use_reduce_only", not _is_dual_side_position) order = self.strategy.order_factory.stop_market( instrument_id=ETHUSDT_PERP_BINANCE.id, @@ -264,7 +348,7 @@ async def test_submit_stop_market_order(self, mocker): submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=self.strategy.id, - position_id=None, + position_id=position_id, order=order, command_id=UUID4(), ts_init=0, @@ -275,27 +359,53 @@ async def test_submit_stop_market_order(self, mocker): await asyncio.sleep(0.3) # Assert - request = mock_send_request.call_args[0] - assert request[0] == "POST" - assert request[1] == "/fapi/v1/order" - assert request[2]["symbol"] == "ETHUSDT" # -PERP was stripped - assert request[2]["side"] == "SELL" - assert request[2]["type"] == "STOP_MARKET" - assert request[2]["timeInForce"] == "GTC" - assert request[2]["quantity"] == "10" - assert request[2]["reduceOnly"] == "True" - assert request[2]["newClientOrderId"] is not None - assert request[2]["stopPrice"] == "10099.00" - assert request[2]["workingType"] == "CONTRACT_PRICE" - assert request[2]["recvWindow"] == "5000" - assert request[2]["signature"] is not None + request = mock_send_request.call_args + assert request[0][0] == HttpMethod.POST + assert request[0][1] == "/fapi/v1/order" + assert request[1]["payload"]["symbol"] == "ETHUSDT" # -PERP was stripped + assert request[1]["payload"]["side"] == "SELL" + assert request[1]["payload"]["type"] == "STOP_MARKET" + assert request[1]["payload"]["timeInForce"] == "GTC" + assert request[1]["payload"]["quantity"] == "10" + if _is_dual_side_position: + assert "reduceOnly" not in request[1]["payload"] + else: + assert request[1]["payload"]["reduceOnly"] == "True" + assert request[1]["payload"]["newClientOrderId"] is not None + assert request[1]["payload"]["stopPrice"] == "10099.00" + assert request[1]["payload"]["workingType"] == "CONTRACT_PRICE" + assert request[1]["payload"]["recvWindow"] == "5000" + assert request[1]["payload"]["signature"] is not None + assert request[1]["payload"]["positionSide"] == expected @pytest.mark.asyncio() - async def test_submit_stop_limit_order(self, mocker): + @pytest.mark.parametrize( + ("_is_dual_side_position", "position_id", "expected"), + [ + # One-way mode + (False, None, "BOTH"), + (False, TestIdStubs.position_id(), "BOTH"), + (False, TestIdStubs.position_id_long(), "BOTH"), + (False, TestIdStubs.position_id_short(), "BOTH"), + (False, TestIdStubs.position_id_both(), "BOTH"), + # Hedge mode + (True, TestIdStubs.position_id_long(), "LONG"), + (True, TestIdStubs.position_id_short(), "SHORT"), + # (True, TestIdStubs.position_id_both(), "BOTH"), + ], + ) + async def test_submit_stop_limit_order( + self, + mocker, + _is_dual_side_position, + position_id, + expected, + ): # Arrange mock_send_request = mocker.patch( target="nautilus_trader.adapters.binance.http.client.BinanceHttpClient.send_request", ) + mocker.patch.object(self.exec_client, "_is_dual_side_position", _is_dual_side_position) order = self.strategy.order_factory.stop_limit( instrument_id=ETHUSDT_PERP_BINANCE.id, @@ -310,7 +420,7 @@ async def test_submit_stop_limit_order(self, mocker): submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=self.strategy.id, - position_id=None, + position_id=position_id, order=order, command_id=UUID4(), ts_init=0, @@ -321,27 +431,50 @@ async def test_submit_stop_limit_order(self, mocker): await asyncio.sleep(0.3) # Assert - request = mock_send_request.call_args[0] - assert request[0] == "POST" - assert request[1] == "/fapi/v1/order" - assert request[2]["symbol"] == "ETHUSDT" # -PERP was stripped - assert request[2]["side"] == "BUY" - assert request[2]["type"] == "STOP" - assert request[2]["timeInForce"] == "GTC" - assert request[2]["quantity"] == "10" - assert request[2]["price"] == "10050.80" - assert request[2]["newClientOrderId"] is not None - assert request[2]["stopPrice"] == "10050.00" - assert request[2]["workingType"] == "MARK_PRICE" - assert request[2]["recvWindow"] == "5000" - assert request[2]["signature"] is not None + request = mock_send_request.call_args + assert request[0][0] == HttpMethod.POST + assert request[0][1] == "/fapi/v1/order" + assert request[1]["payload"]["symbol"] == "ETHUSDT" # -PERP was stripped + assert request[1]["payload"]["side"] == "BUY" + assert request[1]["payload"]["type"] == "STOP" + assert request[1]["payload"]["timeInForce"] == "GTC" + assert request[1]["payload"]["quantity"] == "10" + assert request[1]["payload"]["price"] == "10050.80" + assert request[1]["payload"]["newClientOrderId"] is not None + assert request[1]["payload"]["stopPrice"] == "10050.00" + assert request[1]["payload"]["workingType"] == "MARK_PRICE" + assert request[1]["payload"]["recvWindow"] == "5000" + assert request[1]["payload"]["signature"] is not None + assert request[1]["payload"]["positionSide"] == expected @pytest.mark.asyncio() - async def test_submit_market_if_touched_order(self, mocker): + @pytest.mark.parametrize( + ("_is_dual_side_position", "position_id", "expected"), + [ + # One-way mode + (False, None, "BOTH"), + (False, TestIdStubs.position_id(), "BOTH"), + (False, TestIdStubs.position_id_long(), "BOTH"), + (False, TestIdStubs.position_id_short(), "BOTH"), + (False, TestIdStubs.position_id_both(), "BOTH"), + # Hedge mode + (True, TestIdStubs.position_id_long(), "LONG"), + (True, TestIdStubs.position_id_short(), "SHORT"), + # (True, TestIdStubs.position_id_both(), "BOTH"), + ], + ) + async def test_submit_market_if_touched_order( + self, + mocker, + _is_dual_side_position, + position_id, + expected, + ): # Arrange mock_send_request = mocker.patch( target="nautilus_trader.adapters.binance.http.client.BinanceHttpClient.send_request", ) + mocker.patch.object(self.exec_client, "_is_dual_side_position", _is_dual_side_position) order = self.strategy.order_factory.market_if_touched( instrument_id=ETHUSDT_PERP_BINANCE.id, @@ -354,7 +487,7 @@ async def test_submit_market_if_touched_order(self, mocker): submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=self.strategy.id, - position_id=None, + position_id=position_id, order=order, command_id=UUID4(), ts_init=0, @@ -365,25 +498,48 @@ async def test_submit_market_if_touched_order(self, mocker): await asyncio.sleep(0.3) # Assert - request = mock_send_request.call_args[0] - assert request[0] == "POST" - assert request[1] == "/fapi/v1/order" - assert request[2]["symbol"] == "ETHUSDT" # -PERP was stripped - assert request[2]["side"] == "SELL" - assert request[2]["type"] == "TAKE_PROFIT_MARKET" - assert request[2]["timeInForce"] == "GTC" - assert request[2]["quantity"] == "10" - assert request[2]["newClientOrderId"] is not None - assert request[2]["stopPrice"] == "10099.00" - assert request[2]["recvWindow"] == "5000" - assert request[2]["signature"] is not None + request = mock_send_request.call_args + assert request[0][0] == HttpMethod.POST + assert request[0][1] == "/fapi/v1/order" + assert request[1]["payload"]["symbol"] == "ETHUSDT" # -PERP was stripped + assert request[1]["payload"]["side"] == "SELL" + assert request[1]["payload"]["type"] == "TAKE_PROFIT_MARKET" + assert request[1]["payload"]["timeInForce"] == "GTC" + assert request[1]["payload"]["quantity"] == "10" + assert request[1]["payload"]["newClientOrderId"] is not None + assert request[1]["payload"]["stopPrice"] == "10099.00" + assert request[1]["payload"]["recvWindow"] == "5000" + assert request[1]["payload"]["signature"] is not None + assert request[1]["payload"]["positionSide"] == expected @pytest.mark.asyncio() - async def test_submit_limit_if_touched_order(self, mocker): + @pytest.mark.parametrize( + ("_is_dual_side_position", "position_id", "expected"), + [ + # One-way mode + (False, None, "BOTH"), + (False, TestIdStubs.position_id(), "BOTH"), + (False, TestIdStubs.position_id_long(), "BOTH"), + (False, TestIdStubs.position_id_short(), "BOTH"), + (False, TestIdStubs.position_id_both(), "BOTH"), + # Hedge mode + (True, TestIdStubs.position_id_long(), "LONG"), + (True, TestIdStubs.position_id_short(), "SHORT"), + # (True, TestIdStubs.position_id_both(), "BOTH"), + ], + ) + async def test_submit_limit_if_touched_order( + self, + mocker, + _is_dual_side_position, + position_id, + expected, + ): # Arrange mock_send_request = mocker.patch( target="nautilus_trader.adapters.binance.http.client.BinanceHttpClient.send_request", ) + mocker.patch.object(self.exec_client, "_is_dual_side_position", _is_dual_side_position) order = self.strategy.order_factory.limit_if_touched( instrument_id=ETHUSDT_PERP_BINANCE.id, @@ -397,7 +553,7 @@ async def test_submit_limit_if_touched_order(self, mocker): submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=self.strategy.id, - position_id=None, + position_id=position_id, order=order, command_id=UUID4(), ts_init=0, @@ -408,27 +564,51 @@ async def test_submit_limit_if_touched_order(self, mocker): await asyncio.sleep(0.3) # Assert - request = mock_send_request.call_args[0] - assert request[0] == "POST" - assert request[1] == "/fapi/v1/order" - assert request[2]["symbol"] == "ETHUSDT" # -PERP was stripped - assert request[2]["side"] == "SELL" - assert request[2]["type"] == "TAKE_PROFIT" - assert request[2]["timeInForce"] == "GTC" - assert request[2]["quantity"] == "10" - assert request[2]["price"] == "10050.80" - assert request[2]["newClientOrderId"] is not None - assert request[2]["stopPrice"] == "10099.00" - assert request[2]["workingType"] == "CONTRACT_PRICE" - assert request[2]["recvWindow"] == "5000" - assert request[2]["signature"] is not None + request = mock_send_request.call_args + assert request[0][0] == HttpMethod.POST + assert request[0][1] == "/fapi/v1/order" + assert request[1]["payload"]["symbol"] == "ETHUSDT" # -PERP was stripped + assert request[1]["payload"]["side"] == "SELL" + assert request[1]["payload"]["type"] == "TAKE_PROFIT" + assert request[1]["payload"]["timeInForce"] == "GTC" + assert request[1]["payload"]["quantity"] == "10" + assert request[1]["payload"]["price"] == "10050.80" + assert request[1]["payload"]["newClientOrderId"] is not None + assert request[1]["payload"]["stopPrice"] == "10099.00" + assert request[1]["payload"]["workingType"] == "CONTRACT_PRICE" + assert request[1]["payload"]["recvWindow"] == "5000" + assert request[1]["payload"]["signature"] is not None + assert request[1]["payload"]["positionSide"] == expected @pytest.mark.asyncio() - async def test_trailing_stop_market_order(self, mocker): + @pytest.mark.parametrize( + ("_is_dual_side_position", "position_id", "expected"), + [ + # One-way mode + (False, None, "BOTH"), + (False, TestIdStubs.position_id(), "BOTH"), + (False, TestIdStubs.position_id_long(), "BOTH"), + (False, TestIdStubs.position_id_short(), "BOTH"), + (False, TestIdStubs.position_id_both(), "BOTH"), + # Hedge mode + (True, TestIdStubs.position_id_long(), "LONG"), + (True, TestIdStubs.position_id_short(), "SHORT"), + # (True, TestIdStubs.position_id_both(), "BOTH"), + ], + ) + async def test_trailing_stop_market_order( + self, + mocker, + _is_dual_side_position, + position_id, + expected, + ): # Arrange mock_send_request = mocker.patch( target="nautilus_trader.adapters.binance.http.client.BinanceHttpClient.send_request", ) + mocker.patch.object(self.exec_client, "_is_dual_side_position", _is_dual_side_position) + mocker.patch.object(self.exec_client, "_use_reduce_only", not _is_dual_side_position) order = self.strategy.order_factory.trailing_stop_market( instrument_id=ETHUSDT_PERP_BINANCE.id, @@ -445,7 +625,7 @@ async def test_trailing_stop_market_order(self, mocker): submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=self.strategy.id, - position_id=None, + position_id=position_id, order=order, command_id=UUID4(), ts_init=0, @@ -456,18 +636,22 @@ async def test_trailing_stop_market_order(self, mocker): await asyncio.sleep(0.3) # Assert - request = mock_send_request.call_args[0] - assert request[0] == "POST" - assert request[1] == "/fapi/v1/order" - assert request[2]["symbol"] == "ETHUSDT" # -PERP was stripped - assert request[2]["side"] == "SELL" - assert request[2]["type"] == "TRAILING_STOP_MARKET" - assert request[2]["timeInForce"] == "GTC" - assert request[2]["quantity"] == "10" - assert request[2]["reduceOnly"] == "True" - assert request[2]["newClientOrderId"] is not None - assert request[2]["activationPrice"] == "10000.00" - assert request[2]["callbackRate"] == "1" - assert request[2]["workingType"] == "MARK_PRICE" - assert request[2]["recvWindow"] == "5000" - assert request[2]["signature"] is not None + request = mock_send_request.call_args + assert request[0][0] == HttpMethod.POST + assert request[0][1] == "/fapi/v1/order" + assert request[1]["payload"]["symbol"] == "ETHUSDT" # -PERP was stripped + assert request[1]["payload"]["side"] == "SELL" + assert request[1]["payload"]["type"] == "TRAILING_STOP_MARKET" + assert request[1]["payload"]["timeInForce"] == "GTC" + assert request[1]["payload"]["quantity"] == "10" + if _is_dual_side_position: + assert "reduceOnly" not in request[1]["payload"] + else: + assert request[1]["payload"]["reduceOnly"] == "True" + assert request[1]["payload"]["newClientOrderId"] is not None + assert request[1]["payload"]["activationPrice"] == "10000.00" + assert request[1]["payload"]["callbackRate"] == "1.0" + assert request[1]["payload"]["workingType"] == "MARK_PRICE" + assert request[1]["payload"]["recvWindow"] == "5000" + assert request[1]["payload"]["signature"] is not None + assert request[1]["payload"]["positionSide"] == expected diff --git a/tests/integration_tests/adapters/binance/test_http_account.py b/tests/integration_tests/adapters/binance/test_http_account.py index dc87f1e8bc4c..262c7800ccc9 100644 --- a/tests/integration_tests/adapters/binance/test_http_account.py +++ b/tests/integration_tests/adapters/binance/test_http_account.py @@ -19,6 +19,7 @@ from nautilus_trader.adapters.binance.common.enums import BinanceOrderType from nautilus_trader.adapters.binance.common.enums import BinanceTimeInForce from nautilus_trader.adapters.binance.common.symbol import BinanceSymbol +from nautilus_trader.adapters.binance.futures.http.account import BinanceFuturesAccountHttpAPI from nautilus_trader.adapters.binance.http.account import BinanceOrderHttp from nautilus_trader.adapters.binance.http.client import BinanceHttpClient from nautilus_trader.adapters.binance.spot.http.account import BinanceSpotAccountHttpAPI @@ -39,6 +40,15 @@ def setup(self): self.api = BinanceSpotAccountHttpAPI(self.client, self.clock) + self.futures_client = BinanceHttpClient( + clock=self.clock, + key="SOME_BINANCE_FUTURES_API_KEY", + secret="SOME_BINANCE_FUTURES_API_SECRET", + base_url="https://fapi.binance.com/", # Futures + ) + + self.futures_api = BinanceFuturesAccountHttpAPI(self.futures_client, self.clock) + # COMMON tests @pytest.mark.asyncio() @@ -329,3 +339,17 @@ async def test_query_spot_account_info_sends_expected_request(self, mocker): assert request["method"] == "GET" assert request["url"] == "https://api.binance.com/api/v3/account" assert request["params"].startswith("recvWindow=5000×tamp=") + + @pytest.mark.asyncio() + async def test_query_futures_hedge_mode_sends_expected_request(self, mocker): + # Arrange + mock_send_request = mocker.patch(target="aiohttp.client.ClientSession.request") + + # Act + await self.futures_api.query_futures_hedge_mode(recv_window=5000) + + # Assert + request = mock_send_request.call_args.kwargs + assert request["method"] == "GET" + assert request["url"] == "https://fapi.binance.com/fapi/v1/positionSide/dual" + assert request["params"].startswith("recvWindow=5000×tamp=") diff --git a/tests/integration_tests/adapters/binance/test_parsing_common.py b/tests/integration_tests/adapters/binance/test_parsing_common.py index c4d31cb481f3..9a3d46d074fb 100644 --- a/tests/integration_tests/adapters/binance/test_parsing_common.py +++ b/tests/integration_tests/adapters/binance/test_parsing_common.py @@ -15,8 +15,11 @@ import pytest +from nautilus_trader.adapters.binance.common.enums import BinanceFuturesPositionSide +from nautilus_trader.adapters.binance.common.enums import BinanceKlineInterval from nautilus_trader.adapters.binance.common.enums import BinanceOrderType from nautilus_trader.adapters.binance.common.schemas.market import BinanceCandlestick +from nautilus_trader.adapters.binance.futures.enums import BinanceFuturesEnumParser from nautilus_trader.adapters.binance.futures.schemas.account import BinanceFuturesBalanceInfo from nautilus_trader.adapters.binance.spot.enums import BinanceSpotEnumParser from nautilus_trader.core.datetime import millis_to_nanos @@ -26,6 +29,7 @@ from nautilus_trader.model.enums import BarAggregation from nautilus_trader.model.enums import OrderType from nautilus_trader.model.enums import PriceType +from nautilus_trader.model.identifiers import PositionId from nautilus_trader.test_kit.providers import TestInstrumentProvider @@ -33,8 +37,9 @@ class TestBinanceCommonParsing: - def __init__(self) -> None: + def setup(self): self._spot_enum_parser = BinanceSpotEnumParser() + self._futures_enum_parser = BinanceFuturesEnumParser() @pytest.mark.parametrize( ("order_type", "expected"), @@ -60,7 +65,15 @@ def test_parse_order_type(self, order_type, expected): ("resolution", "expected_type"), [ [ - "1m", + BinanceKlineInterval("1s"), + BarType( + BTCUSDT_BINANCE.id, + BarSpecification(1, BarAggregation.SECOND, PriceType.LAST), + AggregationSource.EXTERNAL, + ), + ], + [ + BinanceKlineInterval("1m"), BarType( BTCUSDT_BINANCE.id, BarSpecification(1, BarAggregation.MINUTE, PriceType.LAST), @@ -68,7 +81,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "3m", + BinanceKlineInterval("3m"), BarType( BTCUSDT_BINANCE.id, BarSpecification(3, BarAggregation.MINUTE, PriceType.LAST), @@ -76,7 +89,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "5m", + BinanceKlineInterval("5m"), BarType( BTCUSDT_BINANCE.id, BarSpecification(5, BarAggregation.MINUTE, PriceType.LAST), @@ -84,7 +97,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "15m", + BinanceKlineInterval("15m"), BarType( BTCUSDT_BINANCE.id, BarSpecification(15, BarAggregation.MINUTE, PriceType.LAST), @@ -92,7 +105,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "30m", + BinanceKlineInterval("30m"), BarType( BTCUSDT_BINANCE.id, BarSpecification(30, BarAggregation.MINUTE, PriceType.LAST), @@ -100,7 +113,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "1h", + BinanceKlineInterval("1h"), BarType( BTCUSDT_BINANCE.id, BarSpecification(1, BarAggregation.HOUR, PriceType.LAST), @@ -108,7 +121,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "2h", + BinanceKlineInterval("2h"), BarType( BTCUSDT_BINANCE.id, BarSpecification(2, BarAggregation.HOUR, PriceType.LAST), @@ -116,7 +129,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "4h", + BinanceKlineInterval("4h"), BarType( BTCUSDT_BINANCE.id, BarSpecification(4, BarAggregation.HOUR, PriceType.LAST), @@ -124,7 +137,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "6h", + BinanceKlineInterval("6h"), BarType( BTCUSDT_BINANCE.id, BarSpecification(6, BarAggregation.HOUR, PriceType.LAST), @@ -132,7 +145,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "8h", + BinanceKlineInterval("8h"), BarType( BTCUSDT_BINANCE.id, BarSpecification(8, BarAggregation.HOUR, PriceType.LAST), @@ -140,7 +153,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "12h", + BinanceKlineInterval("12h"), BarType( BTCUSDT_BINANCE.id, BarSpecification(12, BarAggregation.HOUR, PriceType.LAST), @@ -148,7 +161,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "1d", + BinanceKlineInterval("1d"), BarType( BTCUSDT_BINANCE.id, BarSpecification(1, BarAggregation.DAY, PriceType.LAST), @@ -156,7 +169,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "3d", + BinanceKlineInterval("3d"), BarType( BTCUSDT_BINANCE.id, BarSpecification(3, BarAggregation.DAY, PriceType.LAST), @@ -164,7 +177,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "1w", + BinanceKlineInterval("1w"), BarType( BTCUSDT_BINANCE.id, BarSpecification(1, BarAggregation.WEEK, PriceType.LAST), @@ -172,7 +185,7 @@ def test_parse_order_type(self, order_type, expected): ), ], [ - "1M", + BinanceKlineInterval("1M"), BarType( BTCUSDT_BINANCE.id, BarSpecification(1, BarAggregation.MONTH, PriceType.LAST), @@ -213,6 +226,24 @@ def test_parse_parse_bar_ws(self, resolution, expected_type): # Assert assert bar.bar_type == expected_type + @pytest.mark.parametrize( + ("position_id", "expected"), + [ + [PositionId("P-20240817-BTCUSDT-LONG"), BinanceFuturesPositionSide.LONG], + [PositionId("P-20240817-BTCUSDT-SHORT"), BinanceFuturesPositionSide.SHORT], + [PositionId("P-20240817-BTCUSDT-BOTH"), BinanceFuturesPositionSide.BOTH], + ], + ) + def test_parse_position_id_to_binance_futures_position_side(self, position_id, expected): + # Arrange, Act + print(position_id) + result = self._futures_enum_parser.parse_position_id_to_binance_futures_position_side( + position_id, + ) + + # Assert + assert result == expected + def test_binance_futures_parse_to_balances() -> None: # Arrange From e1764d9e4f7e3c6a09e2286869b699016edb7001 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 08:13:35 +1000 Subject: [PATCH 54/72] Update dependencies and release notes --- RELEASES.md | 9 ++- nautilus_core/Cargo.lock | 76 +++++++++---------- nautilus_core/Cargo.toml | 2 +- poetry.lock | 160 +++++++++++++++++++-------------------- 4 files changed, 125 insertions(+), 122 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index d264648768ff..5b57bba975d2 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -6,13 +6,16 @@ Released on TBD (UTC). - Added `LiveExecEngineConfig.generate_missing_orders` reconciliation config option to align internal and external position states - Added `LogLevel::TRACE` (only available in Rust for debug/development builds) - Added `Actor.subscribe_signal(...)` method and `Data.is_signal(...)` class method (#1853), thanks @faysou +- Added Binance Futures support for `HEDGE` mode (#1846), thanks @DevRoss +- Overhauled and refined error modeling and handling in Rust (#1849), thanks @twitu - Improved `BinanceExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improved `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improved `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions -- Improved `@customdataclass` decorator constructor to allow more positional arguments, thanks @faysou -- Refined error modeling and handling in Rust (#1849), thanks @twitu +- Improved `@customdataclass` decorator constructor to allow more positional arguments (#1850), thanks @faysou +- Improved `@customdataclass` documentation (#1854), thanks @faysou - Upgraded `datafusion` crate to v41.0.0 -- Upgraded `uvloop` to v0.20.0 +- Upgraded `tokio` crate to v1.39.3 +- Upgraded `uvloop` to v0.20.0 (upgrades libuv to v1.48.0) ### Breaking Changes - Changed `VolumeWeightedAveragePrice` calculation formula to use each bars "typical" price (#1842), thanks @evgenii-prusov diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index b2265c663ddd..29914eca0537 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -156,9 +156,9 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "arrow" @@ -418,7 +418,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -429,7 +429,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -611,7 +611,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", "syn_derive", ] @@ -863,7 +863,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1140,7 +1140,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1151,7 +1151,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1533,7 +1533,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1575,7 +1575,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1585,7 +1585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1857,7 +1857,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -3069,7 +3069,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -3146,7 +3146,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -3369,7 +3369,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -3631,7 +3631,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -3644,7 +3644,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -4000,7 +4000,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.74", + "syn 2.0.75", "unicode-ident", ] @@ -4220,7 +4220,7 @@ checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -4446,7 +4446,7 @@ checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -4511,7 +4511,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -4534,7 +4534,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.74", + "syn 2.0.75", "tempfile", "tokio", "url", @@ -4691,7 +4691,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -4713,9 +4713,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -4731,7 +4731,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -4861,7 +4861,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -4960,9 +4960,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.2" +version = "1.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ "backtrace", "bytes", @@ -4984,7 +4984,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -5145,7 +5145,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -5205,7 +5205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -5262,7 +5262,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -5450,7 +5450,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", "wasm-bindgen-shared", ] @@ -5484,7 +5484,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5607,7 +5607,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -5618,7 +5618,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -5839,7 +5839,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] diff --git a/nautilus_core/Cargo.toml b/nautilus_core/Cargo.toml index 91162b022f75..e4282b5e8bb8 100644 --- a/nautilus_core/Cargo.toml +++ b/nautilus_core/Cargo.toml @@ -50,7 +50,7 @@ strum = { version = "0.26.3", features = ["derive"] } thiserror = "1.0.63" thousands = "0.2.0" tracing = "0.1.40" -tokio = { version = "1.39.2", features = ["full"] } +tokio = { version = "1.39.3", features = ["full"] } ustr = { version = "1.0.0", features = ["serde"] } uuid = { version = "1.10.0", features = ["v4"] } diff --git a/poetry.lock b/poetry.lock index 86873413c1bb..ad0051b03126 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,98 +2,98 @@ [[package]] name = "aiohappyeyeballs" -version = "2.3.6" +version = "2.3.7" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.3.6-py3-none-any.whl", hash = "sha256:15dca2611fa78442f1cb54cf07ffb998573f2b4fbeab45ca8554c045665c896b"}, - {file = "aiohappyeyeballs-2.3.6.tar.gz", hash = "sha256:88211068d2a40e0436033956d7de3926ff36d54776f8b1022d6b21320cadae79"}, + {file = "aiohappyeyeballs-2.3.7-py3-none-any.whl", hash = "sha256:337ce4dc0e99eb697c3c5a77d6cb3c52925824d9a67ac0dea7c55b8a2d60b222"}, + {file = "aiohappyeyeballs-2.3.7.tar.gz", hash = "sha256:e794cd29ba6a14078092984e43688212a19081de3a73b6796c2fdeb3706dd6ce"}, ] [[package]] name = "aiohttp" -version = "3.10.3" +version = "3.10.4" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc36cbdedf6f259371dbbbcaae5bb0e95b879bc501668ab6306af867577eb5db"}, - {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85466b5a695c2a7db13eb2c200af552d13e6a9313d7fa92e4ffe04a2c0ea74c1"}, - {file = "aiohttp-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71bb1d97bfe7e6726267cea169fdf5df7658831bb68ec02c9c6b9f3511e108bb"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baec1eb274f78b2de54471fc4c69ecbea4275965eab4b556ef7a7698dee18bf2"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13031e7ec1188274bad243255c328cc3019e36a5a907978501256000d57a7201"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bbc55a964b8eecb341e492ae91c3bd0848324d313e1e71a27e3d96e6ee7e8e8"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8cc0564b286b625e673a2615ede60a1704d0cbbf1b24604e28c31ed37dc62aa"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f817a54059a4cfbc385a7f51696359c642088710e731e8df80d0607193ed2b73"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8542c9e5bcb2bd3115acdf5adc41cda394e7360916197805e7e32b93d821ef93"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:671efce3a4a0281060edf9a07a2f7e6230dca3a1cbc61d110eee7753d28405f7"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0974f3b5b0132edcec92c3306f858ad4356a63d26b18021d859c9927616ebf27"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:44bb159b55926b57812dca1b21c34528e800963ffe130d08b049b2d6b994ada7"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6ae9ae382d1c9617a91647575255ad55a48bfdde34cc2185dd558ce476bf16e9"}, - {file = "aiohttp-3.10.3-cp310-cp310-win32.whl", hash = "sha256:aed12a54d4e1ee647376fa541e1b7621505001f9f939debf51397b9329fd88b9"}, - {file = "aiohttp-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b51aef59370baf7444de1572f7830f59ddbabd04e5292fa4218d02f085f8d299"}, - {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e021c4c778644e8cdc09487d65564265e6b149896a17d7c0f52e9a088cc44e1b"}, - {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:24fade6dae446b183e2410a8628b80df9b7a42205c6bfc2eff783cbeedc224a2"}, - {file = "aiohttp-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc8e9f15939dacb0e1f2d15f9c41b786051c10472c7a926f5771e99b49a5957f"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5a9ec959b5381271c8ec9310aae1713b2aec29efa32e232e5ef7dcca0df0279"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a5d0ea8a6467b15d53b00c4e8ea8811e47c3cc1bdbc62b1aceb3076403d551f"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9ed607dbbdd0d4d39b597e5bf6b0d40d844dfb0ac6a123ed79042ef08c1f87e"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e66d5b506832e56add66af88c288c1d5ba0c38b535a1a59e436b300b57b23e"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fda91ad797e4914cca0afa8b6cccd5d2b3569ccc88731be202f6adce39503189"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:61ccb867b2f2f53df6598eb2a93329b5eee0b00646ee79ea67d68844747a418e"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d881353264e6156f215b3cb778c9ac3184f5465c2ece5e6fce82e68946868ef"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b031ce229114825f49cec4434fa844ccb5225e266c3e146cb4bdd025a6da52f1"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5337cc742a03f9e3213b097abff8781f79de7190bbfaa987bd2b7ceb5bb0bdec"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab3361159fd3dcd0e48bbe804006d5cfb074b382666e6c064112056eb234f1a9"}, - {file = "aiohttp-3.10.3-cp311-cp311-win32.whl", hash = "sha256:05d66203a530209cbe40f102ebaac0b2214aba2a33c075d0bf825987c36f1f0b"}, - {file = "aiohttp-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:70b4a4984a70a2322b70e088d654528129783ac1ebbf7dd76627b3bd22db2f17"}, - {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:166de65e2e4e63357cfa8417cf952a519ac42f1654cb2d43ed76899e2319b1ee"}, - {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7084876352ba3833d5d214e02b32d794e3fd9cf21fdba99cff5acabeb90d9806"}, - {file = "aiohttp-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d98c604c93403288591d7d6d7d6cc8a63459168f8846aeffd5b3a7f3b3e5e09"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d73b073a25a0bb8bf014345374fe2d0f63681ab5da4c22f9d2025ca3e3ea54fc"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8da6b48c20ce78f5721068f383e0e113dde034e868f1b2f5ee7cb1e95f91db57"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a9dcdccf50284b1b0dc72bc57e5bbd3cc9bf019060dfa0668f63241ccc16aa7"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56fb94bae2be58f68d000d046172d8b8e6b1b571eb02ceee5535e9633dcd559c"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf75716377aad2c718cdf66451c5cf02042085d84522aec1f9246d3e4b8641a6"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c51ed03e19c885c8e91f574e4bbe7381793f56f93229731597e4a499ffef2a5"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b84857b66fa6510a163bb083c1199d1ee091a40163cfcbbd0642495fed096204"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c124b9206b1befe0491f48185fd30a0dd51b0f4e0e7e43ac1236066215aff272"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3461d9294941937f07bbbaa6227ba799bc71cc3b22c40222568dc1cca5118f68"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08bd0754d257b2db27d6bab208c74601df6f21bfe4cb2ec7b258ba691aac64b3"}, - {file = "aiohttp-3.10.3-cp312-cp312-win32.whl", hash = "sha256:7f9159ae530297f61a00116771e57516f89a3de6ba33f314402e41560872b50a"}, - {file = "aiohttp-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:e1128c5d3a466279cb23c4aa32a0f6cb0e7d2961e74e9e421f90e74f75ec1edf"}, - {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d1100e68e70eb72eadba2b932b185ebf0f28fd2f0dbfe576cfa9d9894ef49752"}, - {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a541414578ff47c0a9b0b8b77381ea86b0c8531ab37fc587572cb662ccd80b88"}, - {file = "aiohttp-3.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d5548444ef60bf4c7b19ace21f032fa42d822e516a6940d36579f7bfa8513f9c"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba2e838b5e6a8755ac8297275c9460e729dc1522b6454aee1766c6de6d56e5e"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48665433bb59144aaf502c324694bec25867eb6630fcd831f7a893ca473fcde4"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bac352fceed158620ce2d701ad39d4c1c76d114255a7c530e057e2b9f55bdf9f"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0f670502100cdc567188c49415bebba947eb3edaa2028e1a50dd81bd13363f"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b09f38a67679e32d380fe512189ccb0b25e15afc79b23fbd5b5e48e4fc8fd9"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:cd788602e239ace64f257d1c9d39898ca65525583f0fbf0988bcba19418fe93f"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:214277dcb07ab3875f17ee1c777d446dcce75bea85846849cc9d139ab8f5081f"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:32007fdcaab789689c2ecaaf4b71f8e37bf012a15cd02c0a9db8c4d0e7989fa8"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:123e5819bfe1b87204575515cf448ab3bf1489cdeb3b61012bde716cda5853e7"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:812121a201f0c02491a5db335a737b4113151926a79ae9ed1a9f41ea225c0e3f"}, - {file = "aiohttp-3.10.3-cp38-cp38-win32.whl", hash = "sha256:b97dc9a17a59f350c0caa453a3cb35671a2ffa3a29a6ef3568b523b9113d84e5"}, - {file = "aiohttp-3.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:3731a73ddc26969d65f90471c635abd4e1546a25299b687e654ea6d2fc052394"}, - {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38d91b98b4320ffe66efa56cb0f614a05af53b675ce1b8607cdb2ac826a8d58e"}, - {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9743fa34a10a36ddd448bba8a3adc2a66a1c575c3c2940301bacd6cc896c6bf1"}, - {file = "aiohttp-3.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7c126f532caf238031c19d169cfae3c6a59129452c990a6e84d6e7b198a001dc"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926e68438f05703e500b06fe7148ef3013dd6f276de65c68558fa9974eeb59ad"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:434b3ab75833accd0b931d11874e206e816f6e6626fd69f643d6a8269cd9166a"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d35235a44ec38109b811c3600d15d8383297a8fab8e3dec6147477ec8636712a"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59c489661edbd863edb30a8bd69ecb044bd381d1818022bc698ba1b6f80e5dd1"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50544fe498c81cb98912afabfc4e4d9d85e89f86238348e3712f7ca6a2f01dab"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09bc79275737d4dc066e0ae2951866bb36d9c6b460cb7564f111cc0427f14844"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:af4dbec58e37f5afff4f91cdf235e8e4b0bd0127a2a4fd1040e2cad3369d2f06"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b22cae3c9dd55a6b4c48c63081d31c00fc11fa9db1a20c8a50ee38c1a29539d2"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ba562736d3fbfe9241dad46c1a8994478d4a0e50796d80e29d50cabe8fbfcc3f"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f25d6c4e82d7489be84f2b1c8212fafc021b3731abdb61a563c90e37cced3a21"}, - {file = "aiohttp-3.10.3-cp39-cp39-win32.whl", hash = "sha256:b69d832e5f5fa15b1b6b2c8eb6a9fd2c0ec1fd7729cb4322ed27771afc9fc2ac"}, - {file = "aiohttp-3.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:673bb6e3249dc8825df1105f6ef74e2eab779b7ff78e96c15cadb78b04a83752"}, - {file = "aiohttp-3.10.3.tar.gz", hash = "sha256:21650e7032cc2d31fc23d353d7123e771354f2a3d5b05a5647fc30fea214e696"}, + {file = "aiohttp-3.10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:81037ddda8cc0a95c6d8c1b9029d0b19a62db8770c0e239e3bea0109d294ab66"}, + {file = "aiohttp-3.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:71944d4f4090afc07ce96b7029d5a574240e2f39570450df4af0d5b93a5ee64a"}, + {file = "aiohttp-3.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c774f08afecc0a617966f45a9c378456e713a999ee60654d9727617def3e4ee4"}, + {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc990e73613c78ab2930b60266135066f37fdfce6b32dd604f42c5c377ee880a"}, + {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6acd1a908740f708358d240f9a3243cec31a456e3ded65c2cb46f6043bc6735"}, + {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6075e27e7e54fbcd1c129c5699b2d251c885c9892e26d59a0fb7705141c2d14b"}, + {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc98d93d11d860ac823beb6131f292d82efb76f226b5e28a3eab1ec578dfd041"}, + {file = "aiohttp-3.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:201ddf1471567568be381b6d4701e266a768f7eaa2f99ef753f2c9c5e1e3fb5c"}, + {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7d202ec55e61f06b1a1eaf317fba7546855cbf803c13ce7625d462fb8c88e238"}, + {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:96b2e7c110a941c8c1a692703b8ac1013e47f17ee03356c71d55c0a54de2ce38"}, + {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8ba0fbc56c44883bd757ece433f9caadbca67f565934afe9bc53ba3bd99cc368"}, + {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46cc9069da466652bb7b8b3fac1f8ce2e12a9dc0fb11551faa420c4cdbc60abf"}, + {file = "aiohttp-3.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:93a19cd1e9dc703257fda78b8e889c3a08eabaa09f6ff0d867850b03964f80d1"}, + {file = "aiohttp-3.10.4-cp310-cp310-win32.whl", hash = "sha256:8593040bcc8075fc0e817a602bc5d3d74c7bd717619ffc175a8ba0188edebadf"}, + {file = "aiohttp-3.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:326fb5228aadfc395981d9b336d56a698da335897c4143105c73b583d7500839"}, + {file = "aiohttp-3.10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dfe48f477e02ef5ab247c6ac431a6109c69b5c24cb3ccbcd3e27c4fb39691fe4"}, + {file = "aiohttp-3.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6fe78b51852e25d4e20be51ef88c2a0bf31432b9f2223bdbd61c01a0f9253a7"}, + {file = "aiohttp-3.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cc75ff5efbd92301e63a157fddb18a6964a3f40e31c77d57e97dbb9bb3373b4"}, + {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dca39391f45fbb28daa6412f98c625265bf6b512cc41382df61672d1b242f8f4"}, + {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8616dd5ed8b3b4029021b560305041c62e080bb28f238c27c2e150abe3539587"}, + {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d7958ba22854b3f00a7bbb66cde1dc759760ce8a3e6dfe9ea53f06bccaa9aa2"}, + {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a24ac7164a824ef2e8e4e9a9f6debb1f43c44ad7ad04efc6018a6610555666d"}, + {file = "aiohttp-3.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:660ad010b8fd0b26e8edb8ae5c036db5b16baac4278198ad238b11956d920b3d"}, + {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:93ee83008d3e505db9846a5a1f48a002676d8dcc90ee431a9462541c9b81393c"}, + {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77071795efd6ba87f409001141fb05c94ee962b9fca6c8fa1f735c2718512de4"}, + {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ff371ae72a1816c3eeba5c9cff42cb739aaa293fec7d78f180d1c7ee342285b6"}, + {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c253e81f12da97f85d45441e8c6da0d9c12e07db4a7136b0a955df6fc5e4bf51"}, + {file = "aiohttp-3.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2ce101c447cf7ba4b6e5ab07bfa2c0da21cbab66922f78a601f0b84fd7710d72"}, + {file = "aiohttp-3.10.4-cp311-cp311-win32.whl", hash = "sha256:705c311ecf2d30fbcf3570d1a037c657be99095694223488140c47dee4ef2460"}, + {file = "aiohttp-3.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:ebddbfea8a8d6b97f717658fa85a96681a28990072710d3de3a4eba5d6804a37"}, + {file = "aiohttp-3.10.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4d63f42d9c604521b208b754abfafe01218af4a8f6332b43196ee8fe88bbd5"}, + {file = "aiohttp-3.10.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fef7b7bd3a6911b4d148332136d34d3c2aee3d54d354373b1da6d96bc08089a5"}, + {file = "aiohttp-3.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff8606149098935188fe1e135f7e7991e6a36d6fe394fd15939fc57d0aff889"}, + {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb3df1aa83602be9a5e572c834d74c3c8e382208b59a873aabfe4c493c45ed0"}, + {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c4a71d4a5e0cbfd4bfadd13cb84fe2bc76c64d550dc4f22c22008c9354cffb3"}, + {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf61884a604c399458c4a42c8caea000fbcc44255ed89577ff50cb688a0fe8e2"}, + {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2015e4b40bd5dedc8155c2b2d24a2b07963ae02b5772373d0b599a68e38a316b"}, + {file = "aiohttp-3.10.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b06e1a66bf0a1a2d0f12aef25843dfd2093df080d6c1acbc43914bb9c8f36ed3"}, + {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eb898c9ad5a1228a669ebe2e2ba3d76aebe1f7c10b78f09a36000254f049fc2b"}, + {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2d64a5a7539320c3cecb4bca093ea825fcc906f8461cf8b42a7bf3c706ce1932"}, + {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:438c6e1492d060b21285f4b6675b941cf96dd9ef3dfdd59940561029b82e3e1f"}, + {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e99bf118afb2584848dba169a685fe092b338a4fe52ae08c7243d7bc4cc204fe"}, + {file = "aiohttp-3.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dc26781fb95225c6170619dece8b5c6ca7cfb1b0be97b7ee719915773d0c2a9"}, + {file = "aiohttp-3.10.4-cp312-cp312-win32.whl", hash = "sha256:45bb655cb8b3a61e19977183a4e0962051ae90f6d46588ed4addb8232128141c"}, + {file = "aiohttp-3.10.4-cp312-cp312-win_amd64.whl", hash = "sha256:347bbdc48411badc24fe3a13565820bc742db3aa2f9127cd5f48c256caf87e29"}, + {file = "aiohttp-3.10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4ad284cee0fdcdc0216346b849fd53d201b510aff3c48aa3622daec9ada4bf80"}, + {file = "aiohttp-3.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:58df59234be7d7e80548b9482ebfeafdda21948c25cb2873c7f23870c8053dfe"}, + {file = "aiohttp-3.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5f52225af7f91f27b633f73473e9ef0aa8e2112d57b69eaf3aa4479e3ea3bc0e"}, + {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93f1a0e12c321d923c024b56d7dcd8012e60bf30a4b3fb69a88be15dcb9ab80b"}, + {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9e9e9a51dd12f2f71fdbd7f7230dcb75ed8f77d8ac8e07c73b599b6d7027e5c"}, + {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:38bb515f1affc36d3d97b02bf82099925a5785c4a96066ff4400a83ad09d3d5d"}, + {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e685afb0e3b7b861d89cb3690d89eeda221b43095352efddaaa735c6baf87f3"}, + {file = "aiohttp-3.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd5673e3391564871ba6753cf674dcf2051ef19dc508998fe0758a6c7b429a0"}, + {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4b34e5086e1ead3baa740e32adf35cc5e42338e44c4b07f7b62b41ca6d6a5bfd"}, + {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c3fd3b8f0164fb2866400cd6eb9e884ab0dc95f882cf8b25e560ace7350c552d"}, + {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:b95e1694d234f27b4bbf5bdef56bb751974ac5dbe045b1e462bde1fe39421cbe"}, + {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:c031de4dfabe7bb6565743745ab43d20588944ddfc7233360169cab4008eee2f"}, + {file = "aiohttp-3.10.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:03c5a3143d4a82c43a3d82ac77d9cdef527a72f1c04dcca7b14770879f33d196"}, + {file = "aiohttp-3.10.4-cp38-cp38-win32.whl", hash = "sha256:b71722b527445e02168e2d1cf435772731874671a647fa159ad000feea7933b6"}, + {file = "aiohttp-3.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:0fd1f57aac7d01c9c768675d531976d20d5b79d9da67fac87e55d41b4ade05f9"}, + {file = "aiohttp-3.10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:15b36a644d1f44ea3d94a0bbb71e75d5f394a3135dc388a209466e22b711ce64"}, + {file = "aiohttp-3.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:394ddf9d216cf0bd429b223239a0ab628f01a7a1799c93ce4685eedcdd51b9bc"}, + {file = "aiohttp-3.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd33f4d571b4143fc9318c3d9256423579c7d183635acc458a6db81919ae5204"}, + {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5991b80886655e6c785aadf3114d4f86e6bec2da436e2bb62892b9f048450a4"}, + {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92021bf0a4b9ad16851a6c1ca3c86e5b09aecca4f7a2576430c6bbf3114922b1"}, + {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:938e37fd337343c67471098736deb33066d72cec7d8927b9c1b6b4ea807ade9e"}, + {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d697023b16c62f9aeb3ffdfb8ec4ac3afd477388993b9164b47dadbd60e7062"}, + {file = "aiohttp-3.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2f9f07fe6d0d51bd2a788cbb339f1570fd691449c53b5dec83ff838f117703e"}, + {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:50ac670f3fc13ce95e4d6d5a299db9288cc84c663aa630142444ef504756fcf7"}, + {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9bcdd19398212785a9cb82a63a4b75a299998343f3f5732dfd37c1a4275463f9"}, + {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:122c26f0976225aba46f381e3cabb5ef89a08af6503fc30493fb732e578cfa55"}, + {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:d0665e2a346b6b66959f831ffffd8aa71dd07dd2300017d478f5b47573e66cfe"}, + {file = "aiohttp-3.10.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:625a4a9d4b9f80e7bbaaf2ace06341cf701b2fee54232843addf0bb7304597fb"}, + {file = "aiohttp-3.10.4-cp39-cp39-win32.whl", hash = "sha256:5115490112f39f16ae87c1b34dff3e2c95306cf456b1d2af5974c4ac7d2d1ec7"}, + {file = "aiohttp-3.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9b58b2ef7f28a2462ba86acbf3b20371bd80a1faa1cfd82f31968af4ac81ef25"}, + {file = "aiohttp-3.10.4.tar.gz", hash = "sha256:23a5f97e7dd22e181967fb6cb6c3b11653b0fdbbc4bb7739d9b6052890ccab96"}, ] [package.dependencies] From 18e84899a1876682c47a0d3e51b41770a5f3be57 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 08:53:13 +1000 Subject: [PATCH 55/72] Cleanup imports formatting --- nautilus_trader/adapters/binance/futures/http/user.py | 1 - nautilus_trader/adapters/binance/futures/http/wallet.py | 1 - nautilus_trader/adapters/binance/spot/http/user.py | 1 - nautilus_trader/adapters/binance/spot/http/wallet.py | 1 - 4 files changed, 4 deletions(-) diff --git a/nautilus_trader/adapters/binance/futures/http/user.py b/nautilus_trader/adapters/binance/futures/http/user.py index 2ff9c5f20597..6eb3f60b80e9 100644 --- a/nautilus_trader/adapters/binance/futures/http/user.py +++ b/nautilus_trader/adapters/binance/futures/http/user.py @@ -13,7 +13,6 @@ # limitations under the License. # ------------------------------------------------------------------------------------------------- - from nautilus_trader.adapters.binance.common.enums import BinanceAccountType from nautilus_trader.adapters.binance.http.client import BinanceHttpClient from nautilus_trader.adapters.binance.http.user import BinanceUserDataHttpAPI diff --git a/nautilus_trader/adapters/binance/futures/http/wallet.py b/nautilus_trader/adapters/binance/futures/http/wallet.py index a75d36f09967..967faec389b9 100644 --- a/nautilus_trader/adapters/binance/futures/http/wallet.py +++ b/nautilus_trader/adapters/binance/futures/http/wallet.py @@ -13,7 +13,6 @@ # limitations under the License. # ------------------------------------------------------------------------------------------------- - import msgspec from nautilus_trader.adapters.binance.common.enums import BinanceAccountType diff --git a/nautilus_trader/adapters/binance/spot/http/user.py b/nautilus_trader/adapters/binance/spot/http/user.py index 1be0577242cd..56972488b756 100644 --- a/nautilus_trader/adapters/binance/spot/http/user.py +++ b/nautilus_trader/adapters/binance/spot/http/user.py @@ -13,7 +13,6 @@ # limitations under the License. # ------------------------------------------------------------------------------------------------- - from nautilus_trader.adapters.binance.common.enums import BinanceAccountType from nautilus_trader.adapters.binance.http.client import BinanceHttpClient from nautilus_trader.adapters.binance.http.user import BinanceUserDataHttpAPI diff --git a/nautilus_trader/adapters/binance/spot/http/wallet.py b/nautilus_trader/adapters/binance/spot/http/wallet.py index e8dcc2cabdb8..f3c2f7927479 100644 --- a/nautilus_trader/adapters/binance/spot/http/wallet.py +++ b/nautilus_trader/adapters/binance/spot/http/wallet.py @@ -13,7 +13,6 @@ # limitations under the License. # ------------------------------------------------------------------------------------------------- - import msgspec from nautilus_trader.adapters.binance.common.enums import BinanceAccountType From e63a2bda1444bc22563a1c99032c400e9efd8fbc Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 08:53:51 +1000 Subject: [PATCH 56/72] Add error log for missing auction functionality #1476 --- nautilus_trader/backtest/matching_engine.pyx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nautilus_trader/backtest/matching_engine.pyx b/nautilus_trader/backtest/matching_engine.pyx index fd293c7b994e..dcbd27e9266f 100644 --- a/nautilus_trader/backtest/matching_engine.pyx +++ b/nautilus_trader/backtest/matching_engine.pyx @@ -78,6 +78,7 @@ from nautilus_trader.model.events.order cimport OrderTriggered from nautilus_trader.model.events.order cimport OrderUpdated from nautilus_trader.model.functions cimport liquidity_side_to_str from nautilus_trader.model.functions cimport order_type_to_str +from nautilus_trader.model.functions cimport time_in_force_to_str from nautilus_trader.model.identifiers cimport AccountId from nautilus_trader.model.identifiers cimport ClientOrderId from nautilus_trader.model.identifiers cimport InstrumentId @@ -881,7 +882,12 @@ cdef class OrderMatchingEngine: cdef void _process_market_order(self, MarketOrder order): # Check AT_THE_OPEN/AT_THE_CLOSE time in force if order.time_in_force == TimeInForce.AT_THE_OPEN or order.time_in_force == TimeInForce.AT_THE_CLOSE: - self._process_auction_market_order(order) + self._log.error( + f"Market auction for time in force {time_in_force_to_str(order.time_in_force)} " + "is not currently supported", + ) + # TODO: This functionality needs reimplementing + # self._process_auction_market_order(order) return # Check market exists From d700fef9b1facb47451568ee1270d40c95147fde Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 09:33:15 +1000 Subject: [PATCH 57/72] Standardize OptionsContract field grouping --- RELEASES.md | 3 ++- nautilus_core/adapters/src/databento/decode.rs | 8 ++++---- .../infrastructure/src/sql/models/instruments.rs | 4 ++-- nautilus_core/model/src/instruments/options_contract.rs | 6 +++--- nautilus_core/model/src/instruments/stubs.rs | 4 ++-- .../model/src/python/instruments/options_contract.rs | 8 ++++---- nautilus_trader/core/nautilus_pyo3.pyi | 4 ++-- nautilus_trader/model/instruments/options_contract.pxd | 4 ++-- nautilus_trader/model/instruments/options_contract.pyx | 2 +- .../serialization/arrow/implementations/instruments.py | 8 ++++---- 10 files changed, 26 insertions(+), 25 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 5b57bba975d2..6b78acc0e1c9 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -19,6 +19,7 @@ Released on TBD (UTC). ### Breaking Changes - Changed `VolumeWeightedAveragePrice` calculation formula to use each bars "typical" price (#1842), thanks @evgenii-prusov +- Changed `OptionsContract` constructor parameter ordering and Arrow schema (consistently group option kind and strike price) ### Fixes - Fixed `Position` exception type on duplicate fill (should be `KeyError` like `Order`) @@ -73,7 +74,7 @@ Released on 2nd August 2024 (UTC). - Removed `VenueStatus` and all associated methods and schemas (redundant with `InstrumentStatus`) - Renamed `QuoteTick.extract_volume(...)` to `.extract_size(...)` (more accurate terminology) - Changed `InstrumentStatus` params (support Databento `status` schema) -- Changed `InstrumentStatus` arrow schema +- Changed `InstrumentStatus` Arrow schema - Changed `OrderBook` FFI API to take data by reference instead of by value ### Fixes diff --git a/nautilus_core/adapters/src/databento/decode.rs b/nautilus_core/adapters/src/databento/decode.rs index 7b523d73d741..21b92d02be22 100644 --- a/nautilus_core/adapters/src/databento/decode.rs +++ b/nautilus_core/adapters/src/databento/decode.rs @@ -402,10 +402,10 @@ pub fn decode_options_contract_v1( Some(exchange), underlying, parse_option_kind(msg.instrument_class)?, - msg.activation.into(), - msg.expiration.into(), Price::from_raw(msg.strike_price, currency.precision), currency, + msg.activation.into(), + msg.expiration.into(), currency.precision, decode_price(msg.min_price_increment, currency.precision)?, Quantity::new(unit_of_measure_qty, 0), @@ -1046,10 +1046,10 @@ pub fn decode_options_contract( Some(exchange), underlying, parse_option_kind(msg.instrument_class)?, - msg.activation.into(), - msg.expiration.into(), Price::from_raw(msg.strike_price, currency.precision), currency, + msg.activation.into(), + msg.expiration.into(), currency.precision, decode_price(msg.min_price_increment, currency.precision)?, Quantity::new(unit_of_measure_qty, 0), diff --git a/nautilus_core/infrastructure/src/sql/models/instruments.rs b/nautilus_core/infrastructure/src/sql/models/instruments.rs index 9230fccc2674..4730f0c628aa 100644 --- a/nautilus_core/infrastructure/src/sql/models/instruments.rs +++ b/nautilus_core/infrastructure/src/sql/models/instruments.rs @@ -650,10 +650,10 @@ impl<'r> FromRow<'r, PgRow> for OptionsContractModel { exchange, underlying, option_kind, - activation_ns, - expiration_ns, strike_price, currency, + activation_ns, + expiration_ns, price_precision as u8, price_increment, multiplier, diff --git a/nautilus_core/model/src/instruments/options_contract.rs b/nautilus_core/model/src/instruments/options_contract.rs index 85c0c586cf6c..79a9594c4145 100644 --- a/nautilus_core/model/src/instruments/options_contract.rs +++ b/nautilus_core/model/src/instruments/options_contract.rs @@ -47,9 +47,9 @@ pub struct OptionsContract { pub exchange: Option, pub underlying: Ustr, pub option_kind: OptionKind, + pub strike_price: Price, pub activation_ns: UnixNanos, pub expiration_ns: UnixNanos, - pub strike_price: Price, pub currency: Currency, pub price_precision: u8, pub price_increment: Price, @@ -77,10 +77,10 @@ impl OptionsContract { exchange: Option, underlying: Ustr, option_kind: OptionKind, - activation_ns: UnixNanos, - expiration_ns: UnixNanos, strike_price: Price, currency: Currency, + activation_ns: UnixNanos, + expiration_ns: UnixNanos, price_precision: u8, price_increment: Price, multiplier: Quantity, diff --git a/nautilus_core/model/src/instruments/stubs.rs b/nautilus_core/model/src/instruments/stubs.rs index 07c8a866f448..168a22e0740d 100644 --- a/nautilus_core/model/src/instruments/stubs.rs +++ b/nautilus_core/model/src/instruments/stubs.rs @@ -403,10 +403,10 @@ pub fn options_contract_appl() -> OptionsContract { Some(Ustr::from("GMNI")), // Nasdaq GEMX Ustr::from("AAPL"), OptionKind::Call, - UnixNanos::from(activation.timestamp_nanos_opt().unwrap() as u64), - UnixNanos::from(expiration.timestamp_nanos_opt().unwrap() as u64), Price::from("149.0"), Currency::USD(), + UnixNanos::from(activation.timestamp_nanos_opt().unwrap() as u64), + UnixNanos::from(expiration.timestamp_nanos_opt().unwrap() as u64), 2, Price::from("0.01"), Quantity::from(1), diff --git a/nautilus_core/model/src/python/instruments/options_contract.rs b/nautilus_core/model/src/python/instruments/options_contract.rs index af62055d2136..3d67d5524a4a 100644 --- a/nautilus_core/model/src/python/instruments/options_contract.rs +++ b/nautilus_core/model/src/python/instruments/options_contract.rs @@ -40,10 +40,10 @@ impl OptionsContract { asset_class: AssetClass, underlying: String, option_kind: OptionKind, - activation_ns: u64, - expiration_ns: u64, strike_price: Price, currency: Currency, + activation_ns: u64, + expiration_ns: u64, price_precision: u8, price_increment: Price, multiplier: Quantity, @@ -65,10 +65,10 @@ impl OptionsContract { exchange.map(|e| Ustr::from(&e)), underlying.into(), option_kind, - activation_ns.into(), - expiration_ns.into(), strike_price, currency, + activation_ns.into(), + expiration_ns.into(), price_precision, price_increment, multiplier, diff --git a/nautilus_trader/core/nautilus_pyo3.pyi b/nautilus_trader/core/nautilus_pyo3.pyi index dd9c7b27963e..85a935b60e8a 100644 --- a/nautilus_trader/core/nautilus_pyo3.pyi +++ b/nautilus_trader/core/nautilus_pyo3.pyi @@ -1950,10 +1950,10 @@ class OptionsContract: asset_class: AssetClass, underlying: str, option_kind: OptionKind, - activation_ns: int, - expiration_ns: int, strike_price: Price, currency: Currency, + activation_ns: int, + expiration_ns: int, price_precision: int, price_increment: Price, multiplier: Quantity, diff --git a/nautilus_trader/model/instruments/options_contract.pxd b/nautilus_trader/model/instruments/options_contract.pxd index 263aa1b8bd7f..d30f672dafdc 100644 --- a/nautilus_trader/model/instruments/options_contract.pxd +++ b/nautilus_trader/model/instruments/options_contract.pxd @@ -27,12 +27,12 @@ cdef class OptionsContract(Instrument): """The underlying asset for the contract.\n\n:returns: `str`""" cdef readonly OptionKind option_kind """The option kind (PUT | CALL) for the contract.\n\n:returns: `OptionKind`""" + cdef readonly Price strike_price + """The strike price for the contract.\n\n:returns: `Price`""" cdef readonly uint64_t activation_ns """UNIX timestamp (nanoseconds) for contract activation.\n\n:returns: `unit64_t`""" cdef readonly uint64_t expiration_ns """UNIX timestamp (nanoseconds) for contract expiration.\n\n:returns: `unit64_t`""" - cdef readonly Price strike_price - """The strike price for the contract.\n\n:returns: `Price`""" @staticmethod cdef OptionsContract from_dict_c(dict values) diff --git a/nautilus_trader/model/instruments/options_contract.pyx b/nautilus_trader/model/instruments/options_contract.pyx index 6c7d9585e2e9..e33343614126 100644 --- a/nautilus_trader/model/instruments/options_contract.pyx +++ b/nautilus_trader/model/instruments/options_contract.pyx @@ -104,9 +104,9 @@ cdef class OptionsContract(Instrument): Quantity lot_size not None, str underlying, OptionKind option_kind, + Price strike_price not None, uint64_t activation_ns, uint64_t expiration_ns, - Price strike_price not None, uint64_t ts_event, uint64_t ts_init, str exchange = None, diff --git a/nautilus_trader/serialization/arrow/implementations/instruments.py b/nautilus_trader/serialization/arrow/implementations/instruments.py index 1e63f3ba8568..a3f2f59ef5cb 100644 --- a/nautilus_trader/serialization/arrow/implementations/instruments.py +++ b/nautilus_trader/serialization/arrow/implementations/instruments.py @@ -210,17 +210,17 @@ "underlying": pa.dictionary(pa.int16(), pa.string()), "asset_class": pa.dictionary(pa.int8(), pa.string()), "exchange": pa.dictionary(pa.int16(), pa.string()), + "option_kind": pa.dictionary(pa.int8(), pa.string()), + "strike_price": pa.dictionary(pa.int64(), pa.string()), "currency": pa.dictionary(pa.int16(), pa.string()), + "activation_ns": pa.uint64(), + "expiration_ns": pa.uint64(), "price_precision": pa.uint8(), "size_precision": pa.uint8(), "price_increment": pa.dictionary(pa.int16(), pa.string()), "size_increment": pa.dictionary(pa.int16(), pa.string()), "multiplier": pa.dictionary(pa.int16(), pa.string()), "lot_size": pa.dictionary(pa.int16(), pa.string()), - "activation_ns": pa.uint64(), - "expiration_ns": pa.uint64(), - "strike_price": pa.dictionary(pa.int64(), pa.string()), - "option_kind": pa.dictionary(pa.int8(), pa.string()), "info": pa.binary(), "ts_event": pa.uint64(), "ts_init": pa.uint64(), From ec1d2343b04df56cf5124a3e70b1a7d4c2e3d7fb Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 11:41:18 +1000 Subject: [PATCH 58/72] Overhaul order state and position state snapshotting - Configuration removed from `Cache` and `MessageBus` - Configuration removed from `NautilusKernelConfig` - Configuration added to `ExecEngineConfig` - Responsibility moved to `ExecutionEngine` for all environment contexts --- RELEASES.md | 4 + ...nance_futures_testnet_ema_cross_bracket.py | 6 +- .../binance_futures_testnet_market_maker.py | 6 +- ...nce_futures_testnet_orderbook_imbalance.py | 6 +- .../live/binance/binance_spot_ema_cross.py | 6 +- .../live/binance/binance_spot_market_maker.py | 6 +- .../binance_spot_orderbook_imbalance_rust.py | 6 +- examples/live/bybit/bybit_ema_cross.py | 6 +- .../bybit/bybit_ema_cross_bracket_algo.py | 6 +- examples/live/bybit/bybit_market_maker.py | 6 +- .../live/databento/databento_subscriber.py | 6 +- .../binance_futures_testnet_sandbox.py | 6 +- nautilus_core/common/src/timer.rs | 2 +- nautilus_trader/adapters/bybit/data.py | 2 +- .../interactive_brokers/client/error.py | 2 +- .../adapters/interactive_brokers/execution.py | 2 +- nautilus_trader/cache/cache.pxd | 6 +- nautilus_trader/cache/cache.pyx | 27 +----- nautilus_trader/cache/database.pyx | 2 +- nautilus_trader/common/component.pxd | 5 - nautilus_trader/common/component.pyx | 8 -- nautilus_trader/execution/config.py | 18 ++++ nautilus_trader/execution/emulator.pyx | 2 +- nautilus_trader/execution/engine.pxd | 8 ++ nautilus_trader/execution/engine.pyx | 91 +++++++++++++++---- nautilus_trader/execution/manager.pyx | 2 +- nautilus_trader/live/node.py | 54 +---------- nautilus_trader/system/config.py | 17 +--- nautilus_trader/system/kernel.py | 4 - tests/unit_tests/backtest/test_config.py | 4 +- 30 files changed, 151 insertions(+), 175 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 6b78acc0e1c9..6dacd8ab5ba6 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -20,6 +20,10 @@ Released on TBD (UTC). ### Breaking Changes - Changed `VolumeWeightedAveragePrice` calculation formula to use each bars "typical" price (#1842), thanks @evgenii-prusov - Changed `OptionsContract` constructor parameter ordering and Arrow schema (consistently group option kind and strike price) +- Renamed `snapshot_positions_interval` to `snapshot_positions_interval_secs` (more explicitly indicates time units) +- Moved `snapshot_orders` config setting to `ExecEngineConfig` (can now be used for all environment contexts) +- Moved `snapshot_positions` config setting to `ExecEngineConfig` (can now be used for all environment contexts) +- Moved `snapshot_positions_interval_secs` config setting to `ExecEngineConfig` (can now be used for all environment contexts) ### Fixes - Fixed `Position` exception type on duplicate fill (should be `KeyError` like `Order`) diff --git a/examples/live/binance/binance_futures_testnet_ema_cross_bracket.py b/examples/live/binance/binance_futures_testnet_ema_cross_bracket.py index f0bbb2070aa2..48c1e49c8f89 100644 --- a/examples/live/binance/binance_futures_testnet_ema_cross_bracket.py +++ b/examples/live/binance/binance_futures_testnet_ema_cross_bracket.py @@ -50,10 +50,10 @@ database=None, timestamps_as_iso8601=True, flush_on_start=False, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, data_clients={ "BINANCE": BinanceDataClientConfig( api_key=None, # 'BINANCE_API_KEY' env var diff --git a/examples/live/binance/binance_futures_testnet_market_maker.py b/examples/live/binance/binance_futures_testnet_market_maker.py index 79cde9d78dba..5660de5e655f 100644 --- a/examples/live/binance/binance_futures_testnet_market_maker.py +++ b/examples/live/binance/binance_futures_testnet_market_maker.py @@ -55,6 +55,9 @@ ), exec_engine=LiveExecEngineConfig( reconciliation=True, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), cache=CacheConfig( # database=DatabaseConfig(timeout=2), @@ -71,9 +74,6 @@ # autotrim_mins=30, # ), # heartbeat_interval=1.0, - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, # streaming=StreamingConfig(catalog_path="catalog"), data_clients={ "BINANCE": BinanceDataClientConfig( diff --git a/examples/live/binance/binance_futures_testnet_orderbook_imbalance.py b/examples/live/binance/binance_futures_testnet_orderbook_imbalance.py index 38562d41dcb0..809941cd1613 100644 --- a/examples/live/binance/binance_futures_testnet_orderbook_imbalance.py +++ b/examples/live/binance/binance_futures_testnet_orderbook_imbalance.py @@ -49,15 +49,15 @@ reconciliation=True, reconciliation_lookback_mins=1440, filter_position_reports=True, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), cache=CacheConfig( database=None, timestamps_as_iso8601=True, flush_on_start=False, ), - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, data_clients={ "BINANCE": BinanceDataClientConfig( api_key=None, # 'BINANCE_API_KEY' env var diff --git a/examples/live/binance/binance_spot_ema_cross.py b/examples/live/binance/binance_spot_ema_cross.py index 851feb47c57e..8efe28dad69e 100644 --- a/examples/live/binance/binance_spot_ema_cross.py +++ b/examples/live/binance/binance_spot_ema_cross.py @@ -44,6 +44,9 @@ exec_engine=LiveExecEngineConfig( reconciliation=True, reconciliation_lookback_mins=1440, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), # cache=CacheConfig( # database=DatabaseConfig(), @@ -59,9 +62,6 @@ # autotrim_mins=1, # ), # heartbeat_interval=1.0, - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, data_clients={ "BINANCE": BinanceDataClientConfig( api_key=None, # 'BINANCE_API_KEY' env var diff --git a/examples/live/binance/binance_spot_market_maker.py b/examples/live/binance/binance_spot_market_maker.py index 9e0351b93ec2..70f153ccd264 100644 --- a/examples/live/binance/binance_spot_market_maker.py +++ b/examples/live/binance/binance_spot_market_maker.py @@ -49,6 +49,9 @@ ), exec_engine=LiveExecEngineConfig( reconciliation=True, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), cache=CacheConfig( # database=DatabaseConfig(), @@ -67,9 +70,6 @@ # autotrim_mins=30, # ), # heartbeat_interval=1.0, - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, # streaming=StreamingConfig(catalog_path="catalog"), data_clients={ "BINANCE": BinanceDataClientConfig( diff --git a/examples/live/binance/binance_spot_orderbook_imbalance_rust.py b/examples/live/binance/binance_spot_orderbook_imbalance_rust.py index 255944da0daa..2599109ac346 100644 --- a/examples/live/binance/binance_spot_orderbook_imbalance_rust.py +++ b/examples/live/binance/binance_spot_orderbook_imbalance_rust.py @@ -49,15 +49,15 @@ reconciliation=True, reconciliation_lookback_mins=1440, filter_position_reports=True, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), cache=CacheConfig( database=None, timestamps_as_iso8601=True, flush_on_start=False, ), - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, data_clients={ "BINANCE": BinanceDataClientConfig( api_key=None, # 'BINANCE_API_KEY' env var diff --git a/examples/live/bybit/bybit_ema_cross.py b/examples/live/bybit/bybit_ema_cross.py index c2b3b8f6159a..26b187313669 100644 --- a/examples/live/bybit/bybit_ema_cross.py +++ b/examples/live/bybit/bybit_ema_cross.py @@ -48,6 +48,9 @@ exec_engine=LiveExecEngineConfig( reconciliation=True, reconciliation_lookback_mins=1440, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), # cache=CacheConfig( # database=DatabaseConfig(), @@ -62,9 +65,6 @@ # autotrim_mins=1, # ), # heartbeat_interval=1.0, - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, data_clients={ "BYBIT": BybitDataClientConfig( api_key=None, # 'BYBIT_API_KEY' env var diff --git a/examples/live/bybit/bybit_ema_cross_bracket_algo.py b/examples/live/bybit/bybit_ema_cross_bracket_algo.py index 10adef72291c..a1fe6e156a14 100644 --- a/examples/live/bybit/bybit_ema_cross_bracket_algo.py +++ b/examples/live/bybit/bybit_ema_cross_bracket_algo.py @@ -51,6 +51,9 @@ exec_engine=LiveExecEngineConfig( reconciliation=True, reconciliation_lookback_mins=1440, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), risk_engine=LiveRiskEngineConfig(debug=True), # cache=CacheConfig( @@ -66,9 +69,6 @@ # autotrim_mins=1, # ), # heartbeat_interval=1.0, - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, data_clients={ "BYBIT": BybitDataClientConfig( api_key=None, # 'BYBIT_API_KEY' env var diff --git a/examples/live/bybit/bybit_market_maker.py b/examples/live/bybit/bybit_market_maker.py index 6a69a2eb9829..749ea19c732a 100644 --- a/examples/live/bybit/bybit_market_maker.py +++ b/examples/live/bybit/bybit_market_maker.py @@ -57,6 +57,9 @@ exec_engine=LiveExecEngineConfig( reconciliation=True, reconciliation_lookback_mins=1440, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), cache=CacheConfig( # database=DatabaseConfig(), @@ -76,9 +79,6 @@ # autotrim_mins=30, # ), # heartbeat_interval=1.0, - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, data_clients={ "BYBIT": BybitDataClientConfig( api_key=None, # 'BYBIT_API_KEY' env var diff --git a/examples/live/databento/databento_subscriber.py b/examples/live/databento/databento_subscriber.py index c3246773c3d1..d1d5f3c2c370 100644 --- a/examples/live/databento/databento_subscriber.py +++ b/examples/live/databento/databento_subscriber.py @@ -56,6 +56,9 @@ exec_engine=LiveExecEngineConfig( reconciliation=False, # Not applicable inflight_check_interval_ms=0, # Not applicable + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), cache=CacheConfig( database=DatabaseConfig(), @@ -74,9 +77,6 @@ # autotrim_mins=30, # ), # heartbeat_interval=1.0, - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, data_clients={ DATABENTO: DatabentoDataClientConfig( api_key=None, # 'DATABENTO_API_KEY' env var diff --git a/examples/sandbox/binance_futures_testnet_sandbox.py b/examples/sandbox/binance_futures_testnet_sandbox.py index 79ac85765cd0..95178f0755da 100644 --- a/examples/sandbox/binance_futures_testnet_sandbox.py +++ b/examples/sandbox/binance_futures_testnet_sandbox.py @@ -57,6 +57,9 @@ async def main(): reconciliation=True, reconciliation_lookback_mins=1440, filter_position_reports=True, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), cache=CacheConfig( # database=DatabaseConfig(timeout=2), @@ -73,9 +76,6 @@ async def main(): # autotrim_mins=30, # ), # heartbeat_interval=1.0, - # snapshot_orders=True, - # snapshot_positions=True, - # snapshot_positions_interval=5.0, data_clients={ "BINANCE": BinanceDataClientConfig( api_key=None, # 'BINANCE_API_KEY' env var diff --git a/nautilus_core/common/src/timer.rs b/nautilus_core/common/src/timer.rs index b60fbc73d280..0bb47b5affac 100644 --- a/nautilus_core/common/src/timer.rs +++ b/nautilus_core/common/src/timer.rs @@ -287,7 +287,7 @@ impl LiveTimer { // Floor the next time to the nearest microsecond which is within the timers accuracy let mut next_time_ns = UnixNanos::from(floor_to_nearest_microsecond(next_time_ns)); - // Setup oneshot channel for cancelling timer task + // Setup oneshot channel for canceling timer task let (cancel_tx, mut cancel_rx) = oneshot::channel(); self.canceler = Some(cancel_tx); diff --git a/nautilus_trader/adapters/bybit/data.py b/nautilus_trader/adapters/bybit/data.py index bbab78b98737..295c0e3d0342 100644 --- a/nautilus_trader/adapters/bybit/data.py +++ b/nautilus_trader/adapters/bybit/data.py @@ -222,7 +222,7 @@ async def _connect(self) -> None: async def _disconnect(self) -> None: if self._update_instruments_task: - self._log.debug("Cancelling `update_instruments` task") + self._log.debug("Canceling `update_instruments` task") self._update_instruments_task.cancel() self._update_instruments_task = None for ws_client in self._ws_clients.values(): diff --git a/nautilus_trader/adapters/interactive_brokers/client/error.py b/nautilus_trader/adapters/interactive_brokers/client/error.py index d37293e3b26d..7b7c7848d15b 100644 --- a/nautilus_trader/adapters/interactive_brokers/client/error.py +++ b/nautilus_trader/adapters/interactive_brokers/client/error.py @@ -125,7 +125,7 @@ async def _handle_subscription_error( ) -> None: """ Handle errors specific to data subscriptions. Processes subscription-related - errors and takes appropriate actions, such as cancelling the subscription or + errors and takes appropriate actions, such as canceling the subscription or clearing flags. Parameters diff --git a/nautilus_trader/adapters/interactive_brokers/execution.py b/nautilus_trader/adapters/interactive_brokers/execution.py index f4a8578b0781..56764af190e3 100644 --- a/nautilus_trader/adapters/interactive_brokers/execution.py +++ b/nautilus_trader/adapters/interactive_brokers/execution.py @@ -261,7 +261,7 @@ async def generate_order_status_report( break if report is None: self._log.warning( - f"Order {client_order_id=}, {venue_order_id} not found, Cancelling...", + f"Order {client_order_id=}, {venue_order_id} not found, canceling...", ) self._on_order_status( order_ref=client_order_id.value, diff --git a/nautilus_trader/cache/cache.pxd b/nautilus_trader/cache/cache.pxd index 2b22996cbe5f..62a5a219cb90 100644 --- a/nautilus_trader/cache/cache.pxd +++ b/nautilus_trader/cache/cache.pxd @@ -106,14 +106,12 @@ cdef class Cache(CacheFacade): cdef set _index_exec_algorithms cdef bint _drop_instruments_on_reset + cdef readonly bint has_backing + """If the cache has a database backing.\n\n:returns: `bool`""" cdef readonly int tick_capacity """The caches tick capacity.\n\n:returns: `int`""" cdef readonly int bar_capacity """The caches bar capacity.\n\n:returns: `int`""" - cdef readonly bint snapshot_orders - """If order state snapshots should be persisted.\n\n:returns: `bool`""" - cdef readonly bint snapshot_positions - """If position state snapshots should be persisted.\n\n:returns: `bool`""" cpdef void cache_general(self) cpdef void cache_currencies(self) diff --git a/nautilus_trader/cache/cache.pyx b/nautilus_trader/cache/cache.pyx index b6f15ba9310e..75b06f344000 100644 --- a/nautilus_trader/cache/cache.pyx +++ b/nautilus_trader/cache/cache.pyx @@ -81,10 +81,6 @@ cdef class Cache(CacheFacade): The database adapter for the cache. If ``None`` then will bypass persistence. config : CacheConfig, optional The cache configuration. - snapshot_orders : bool, default False - If order state snapshots should be persisted. - snapshot_positions : bool, default False - If position state snapshots should be persisted. Raises ------ @@ -95,10 +91,8 @@ cdef class Cache(CacheFacade): def __init__( self, CacheDatabaseFacade database: CacheDatabaseFacade | None = None, - bint snapshot_orders: bool = False, - bint snapshot_positions: bool = False, config: CacheConfig | None = None, - ): + ) -> None: if config is None: config = CacheConfig() Condition.type(config, CacheConfig, "config") @@ -109,10 +103,9 @@ cdef class Cache(CacheFacade): # Configuration self._drop_instruments_on_reset = config.drop_instruments_on_reset + self.has_backing = database is not None self.tick_capacity = config.tick_capacity self.bar_capacity = config.bar_capacity - self.snapshot_orders = snapshot_orders - self.snapshot_positions = snapshot_positions # Caches self._general: dict[str, bytes] = {} @@ -1565,8 +1558,6 @@ cdef class Cache(CacheFacade): # Update database self._database.add_order(order, position_id, client_id) - if self.snapshot_orders: - self._database.snapshot_order_state(order) cpdef void add_order_list(self, OrderList order_list): """ @@ -1702,12 +1693,6 @@ cdef class Cache(CacheFacade): # Update database self._database.add_position(position) - if self.snapshot_positions: - self._database.snapshot_position_state( - position, - position.ts_last, - self.calculate_unrealized_pnl(position), - ) cpdef void snapshot_position(self, Position position): """ @@ -1860,8 +1845,6 @@ cdef class Cache(CacheFacade): # Update database self._database.update_order(order) - if self.snapshot_orders: - self._database.snapshot_order_state(order) cpdef void update_order_pending_cancel_local(self, Order order): """ @@ -1901,12 +1884,6 @@ cdef class Cache(CacheFacade): # Update database self._database.update_position(position) - if self.snapshot_positions: - self._database.snapshot_position_state( - position, - position.ts_last, - self.calculate_unrealized_pnl(position), - ) cpdef void update_actor(self, Actor actor): """ diff --git a/nautilus_trader/cache/database.pyx b/nautilus_trader/cache/database.pyx index 2a81af387920..2a3e8af78310 100644 --- a/nautilus_trader/cache/database.pyx +++ b/nautilus_trader/cache/database.pyx @@ -132,7 +132,7 @@ cdef class CacheDatabaseAdapter(CacheDatabaseFacade): UUID4 instance_id not None, Serializer serializer not None, config: CacheConfig | None = None, - ): + ) -> None: if config is None: config = CacheConfig() Condition.type(config, CacheConfig, "config") diff --git a/nautilus_trader/common/component.pxd b/nautilus_trader/common/component.pxd index 2716f7f9df56..f74567dcf643 100644 --- a/nautilus_trader/common/component.pxd +++ b/nautilus_trader/common/component.pxd @@ -255,7 +255,6 @@ cdef class MessageBus: cdef dict[UUID4, object] _correlation_index cdef tuple[type] _publishable_types cdef set[type] _streaming_types - cdef bint _has_backing cdef bint _resolved cdef readonly TraderId trader_id @@ -264,10 +263,6 @@ cdef class MessageBus: """The serializer for the bus.\n\n:returns: `Serializer`""" cdef readonly bint has_backing """If the message bus has a database backing.\n\n:returns: `bool`""" - cdef readonly bint snapshot_orders - """If order state snapshots should be published externally.\n\n:returns: `bool`""" - cdef readonly bint snapshot_positions - """If position state snapshots should be published externally.\n\n:returns: `bool`""" cdef readonly uint64_t sent_count """The count of messages sent through the bus.\n\n:returns: `uint64_t`""" cdef readonly uint64_t req_count diff --git a/nautilus_trader/common/component.pyx b/nautilus_trader/common/component.pyx index a9255e9975da..cd18a85fbf7b 100644 --- a/nautilus_trader/common/component.pyx +++ b/nautilus_trader/common/component.pyx @@ -1971,10 +1971,6 @@ cdef class MessageBus: The serializer for database operations. database : nautilus_pyo3.RedisMessageBusDatabase, optional The backing database for the message bus. - snapshot_orders : bool, default False - If order state snapshots should be published externally. - snapshot_positions : bool, default False - If position state snapshots should be published externally. config : MessageBusConfig, optional The configuration for the message bus. @@ -1997,8 +1993,6 @@ cdef class MessageBus: str name = None, Serializer serializer = None, database: nautilus_pyo3.RedisMessageBusDatabase | None = None, - bint snapshot_orders: bool = False, - bint snapshot_positions: bool = False, config: Any | None = None, ) -> None: # Temporary fix for import error @@ -2016,8 +2010,6 @@ cdef class MessageBus: self.trader_id = trader_id self.serializer = serializer self.has_backing = database is not None - self.snapshot_orders = snapshot_orders - self.snapshot_positions = snapshot_positions self._clock = clock self._log = Logger(name) diff --git a/nautilus_trader/execution/config.py b/nautilus_trader/execution/config.py index a5c1aa5bd8e3..1428a02928a1 100644 --- a/nautilus_trader/execution/config.py +++ b/nautilus_trader/execution/config.py @@ -20,6 +20,7 @@ import msgspec from nautilus_trader.common.config import NautilusConfig +from nautilus_trader.common.config import PositiveFloat from nautilus_trader.common.config import msgspec_encoding_hook from nautilus_trader.common.config import resolve_config_path from nautilus_trader.common.config import resolve_path @@ -35,12 +36,29 @@ class ExecEngineConfig(NautilusConfig, frozen=True): ---------- load_cache : bool, default True If the cache should be loaded on initialization. + snapshot_orders : bool, default False + If order state snapshot lists are persisted to a backing database. + Snapshots will be taken at every order state update (when events are applied). + snapshot_positions : bool, default False + If position state snapshot lists are persisted to a backing database. + Snapshots will be taken at position opened, changed and closed (when events are applied). + To include the unrealized PnL in the snapshot then quotes for the positions instrument must + be available in the cache. + snapshot_positions_interval_secs : PositiveFloat, optional + The interval (seconds) at which *additional* position state snapshots are persisted to a + backing database. + If ``None`` then no additional snapshots will be taken. + To include unrealized PnL in these snapshots, quotes for the position's instrument must be + available in the cache. debug : bool, default False If debug mode is active (will provide extra debug logging). """ load_cache: bool = True + snapshot_orders: bool = False + snapshot_positions: bool = False + snapshot_positions_interval_secs: PositiveFloat | None = None debug: bool = False diff --git a/nautilus_trader/execution/emulator.pyx b/nautilus_trader/execution/emulator.pyx index 0b50e1393356..a34c2ce55f7d 100644 --- a/nautilus_trader/execution/emulator.pyx +++ b/nautilus_trader/execution/emulator.pyx @@ -574,7 +574,7 @@ cdef class OrderEmulator(Actor): return if self.debug: - self._log.info(f"Cancelling order {order.client_order_id!r}", LogColor.MAGENTA) + self._log.info(f"Canceling order {order.client_order_id!r}", LogColor.MAGENTA) # Remove emulation trigger order.emulation_trigger = TriggerType.NO_TRIGGER diff --git a/nautilus_trader/execution/engine.pxd b/nautilus_trader/execution/engine.pxd index 2c03de8429c3..dc644c134b43 100644 --- a/nautilus_trader/execution/engine.pxd +++ b/nautilus_trader/execution/engine.pxd @@ -50,9 +50,16 @@ cdef class ExecutionEngine(Component): cdef readonly dict[Venue, ExecutionClient] _routing_map cdef readonly dict[StrategyId, OmsType] _oms_overrides cdef readonly dict[InstrumentId, StrategyId] _external_order_claims + cdef readonly str snapshot_positions_timer_name cdef readonly bint debug """If debug mode is active (will provide extra debug logging).\n\n:returns: `bool`""" + cdef readonly bint snapshot_orders + """If order state snapshots should be persisted.\n\n:returns: `bool`""" + cdef readonly bint snapshot_positions + """If position state snapshots should be persisted.\n\n:returns: `bool`""" + cdef readonly double snapshot_positions_interval_secs + """The interval (seconds) at which additional position state snapshots are persisted.\n\n:returns: `double`""" cdef readonly int command_count """The total count of commands received by the engine.\n\n:returns: `int`""" cdef readonly int event_count @@ -122,3 +129,4 @@ cdef class ExecutionEngine(Component): cpdef void _flip_position(self, Instrument instrument, Position position, OrderFilled fill, OmsType oms_type) cpdef void _publish_order_snapshot(self, Order order) cpdef void _publish_position_snapshot(self, Position position) + cpdef void _snapshot_open_positions(self) diff --git a/nautilus_trader/execution/engine.pyx b/nautilus_trader/execution/engine.pyx index fcecb15439d3..06cd28d4c3ce 100644 --- a/nautilus_trader/execution/engine.pyx +++ b/nautilus_trader/execution/engine.pyx @@ -52,6 +52,7 @@ from nautilus_trader.common.component cimport MessageBus from nautilus_trader.common.generators cimport PositionIdGenerator from nautilus_trader.core.correctness cimport Condition from nautilus_trader.core.fsm cimport InvalidStateTrigger +from nautilus_trader.core.rust.core cimport secs_to_nanos from nautilus_trader.core.rust.model cimport ContingencyType from nautilus_trader.core.rust.model cimport OmsType from nautilus_trader.core.rust.model cimport PositionSide @@ -145,6 +146,10 @@ cdef class ExecutionEngine(Component): # Settings self.debug: bool = config.debug + self.snapshot_orders = config.snapshot_orders + self.snapshot_positions = config.snapshot_positions + self.snapshot_positions_interval_secs = config.snapshot_positions_interval_secs or 0 + self.snapshot_positions_timer_name = "ExecEngine_SNAPSHOT_POSITIONS" # Counters self.command_count: int = 0 @@ -536,6 +541,19 @@ cdef class ExecutionEngine(Component): for client in self._clients.values(): client.start() + if self.snapshot_positions_interval_secs and self.snapshot_positions_timer_name not in self._clock.timer_names: + self._log.info( + f"Starting position snapshots timer at {self.snapshot_positions_interval_secs} second intervals", + ) + interval_ns = secs_to_nanos(self.snapshot_positions_interval_secs) + self._clock.set_timer_ns( + name=self.snapshot_positions_timer_name, + interval_ns=interval_ns, + start_time_ns=0, # TBD if should align to nearest second boundary + stop_time_ns=0, # Run as long as execution engine is running + callback=self._snapshot_open_positions, + ) + self._on_start() cpdef void _stop(self): @@ -543,6 +561,10 @@ cdef class ExecutionEngine(Component): for client in self._clients.values(): client.stop() + if self.snapshot_positions_interval_secs and self.snapshot_positions_timer_name in self._clock.timer_names: + self._log.info(f"Canceling position snapshots timer") + self._clock.cancel_timer(self.snapshot_positions_timer_name) + self._on_stop() cpdef void _reset(self): @@ -711,7 +733,7 @@ cdef class ExecutionEngine(Component): topic=f"events.order.{order.strategy_id}", msg=denied, ) - if self._msgbus.has_backing and self._msgbus.snapshot_orders: + if self.snapshot_orders and (self._cache.has_backing or self._msgbus.has_backing): self._publish_order_snapshot(order) # -- COMMAND HANDLERS ----------------------------------------------------------------------------- @@ -759,7 +781,7 @@ cdef class ExecutionEngine(Component): if not self._cache.order_exists(order.client_order_id): # Cache order self._cache.add_order(order, command.position_id, command.client_id) - if self._msgbus.has_backing and self._msgbus.snapshot_orders: + if self.snapshot_orders and (self._cache.has_backing or self._msgbus.has_backing): self._publish_order_snapshot(order) cdef Instrument instrument = self._cache.instrument(order.instrument_id) @@ -790,7 +812,7 @@ cdef class ExecutionEngine(Component): if not self._cache.order_exists(order.client_order_id): # Cache order self._cache.add_order(order, command.position_id, command.client_id) - if self._msgbus.has_backing and self._msgbus.snapshot_orders: + if self.snapshot_orders and (self._cache.has_backing or self._msgbus.has_backing): self._publish_order_snapshot(order) cdef Instrument instrument = self._cache.instrument(command.instrument_id) @@ -1019,7 +1041,7 @@ cdef class ExecutionEngine(Component): topic=f"events.order.{event.strategy_id}", msg=event, ) - if self._msgbus.has_backing and self._msgbus.snapshot_orders: + if self.snapshot_orders and (self._cache.has_backing or self._msgbus.has_backing): self._publish_order_snapshot(order) cpdef void _handle_order_fill(self, Order order, OrderFilled fill, OmsType oms_type): @@ -1066,15 +1088,14 @@ cdef class ExecutionEngine(Component): if position is None: position = Position(instrument, fill) self._cache.add_position(position, oms_type) - if self._msgbus.has_backing and self._msgbus.snapshot_positions: + if self.snapshot_positions and (self._cache.has_backing or self._msgbus.has_backing): self._publish_position_snapshot(position) else: try: + # Always snapshot opening positions to handle NETTING OMS self._cache.snapshot_position(position) position.apply(fill) self._cache.update_position(position) - if self._msgbus.has_backing and self._msgbus.snapshot_positions: - self._publish_position_snapshot(position) except KeyError as e: # Protected against duplicate OrderFilled self._log.exception(f"Error on applying {fill!r} to {position!r}", e) @@ -1103,7 +1124,7 @@ cdef class ExecutionEngine(Component): return # Not re-raising to avoid crashing engine self._cache.update_position(position) - if self._msgbus.has_backing and self._msgbus.snapshot_positions: + if self.snapshot_positions and (self._cache.has_backing or self._msgbus.has_backing): self._publish_position_snapshot(position) cdef PositionEvent event @@ -1221,19 +1242,51 @@ cdef class ExecutionEngine(Component): self._open_position(instrument, None, fill_split2, oms_type) cpdef void _publish_order_snapshot(self, Order order): - if self._msgbus.serializer is not None: - self._msgbus.publish_c( - topic=f"snapshots:orders:{order.client_order_id.to_str()}", - msg=self._msgbus.serializer.serialize(order.to_dict()) - ) + if self._cache.has_backing: + # Persist position snapshot to database + self._cache.snapshot_order_state(order) + + if self._msgbus.has_backing: + # Publish order snapshot on message bus + if self._msgbus.serializer is not None: + self._msgbus.publish_c( + topic=f"snapshots:orders:{order.client_order_id.to_str()}", + msg=self._msgbus.serializer.serialize(order.to_dict()) + ) cpdef void _publish_position_snapshot(self, Position position): - cdef dict position_state = position.to_dict() cdef Money unrealized_pnl = self._cache.calculate_unrealized_pnl(position) + + if self._cache.has_backing: + # Persist position snapshot to database + self._cache.snapshot_position_state( + position, + position.ts_last, + unrealized_pnl, + ) + + cdef dict[str, object] position_state = position.to_dict() if unrealized_pnl is not None: position_state["unrealized_pnl"] = str(unrealized_pnl) - if self._msgbus.serializer is not None: - self._msgbus.publish( - topic=f"snapshots:positions:{position.id}", - msg=self._msgbus.serializer.serialize(position_state), - ) + + # TODO: Experimental internal message bus publishing + self._msgbus.publish_c( + topic=f"snapshots.positions.{position.id}", + msg=position_state, + external_pub=False, + ) + + if self._msgbus.has_backing: + # Publish position snapshot on message bus + if self._msgbus.serializer is not None: + self._msgbus.publish_c( + topic=f"snapshots:positions:{position.id}", + msg=self._msgbus.serializer.serialize(position_state), + ) + + cpdef void _snapshot_open_positions(self): + cdef list[Position] open_positions = self._cache.positions_open() + + cdef Position position + for position in open_positions: + self._publish_position_snapshot(position) diff --git a/nautilus_trader/execution/manager.pyx b/nautilus_trader/execution/manager.pyx index f31155bbedd7..abc5d7495582 100644 --- a/nautilus_trader/execution/manager.pyx +++ b/nautilus_trader/execution/manager.pyx @@ -190,7 +190,7 @@ cdef class OrderManager: return if self.debug: - self._log.info(f"Cancelling order {order}", LogColor.MAGENTA) + self._log.info(f"Canceling order {order}", LogColor.MAGENTA) self._submit_order_commands.pop(order.client_order_id, None) diff --git a/nautilus_trader/live/node.py b/nautilus_trader/live/node.py index a1fb3d1f27ff..a194986015f0 100644 --- a/nautilus_trader/live/node.py +++ b/nautilus_trader/live/node.py @@ -92,7 +92,6 @@ def __init__( # Async tasks self._task_streaming: asyncio.Future | None = None self._task_heartbeats: asyncio.Task | None = None - self._task_position_snapshots: asyncio.Task | None = None @property def trader_id(self) -> TraderId: @@ -336,10 +335,6 @@ async def run_async(self) -> None: self._task_heartbeats = asyncio.create_task( self.maintain_heartbeat(self._config.heartbeat_interval), ) - if self._config.snapshot_positions_interval: - self._task_position_snapshots = asyncio.create_task( - self.snapshot_open_positions(self._config.snapshot_positions_interval), - ) await asyncio.gather(*tasks) except asyncio.CancelledError as e: @@ -373,46 +368,6 @@ async def maintain_heartbeat(self, interval: float) -> None: # Catch-all exceptions for development purposes (unexpected errors) self.kernel.logger.error(str(e)) - async def snapshot_open_positions(self, interval: float) -> None: - """ - Snapshot the state of all open positions at the configured interval. - - Parameters - ---------- - interval : float - The interval (seconds) between open position state snapshotting. - - """ - self.kernel.logger.info( - f"Starting task: snapshot open positions at {interval}s intervals", - LogColor.BLUE, - ) - try: - while True: - await asyncio.sleep(interval) - open_positions = self.kernel.cache.positions_open() - for position in open_positions: - if self._has_cache_backing: - self.cache.snapshot_position_state( - position=position, - ts_snapshot=self.kernel.clock.timestamp_ns(), - ) - if self._has_msgbus_backing: - # TODO: Consolidate this with the cache - position_state = position.to_dict() - unrealized_pnl = self.kernel.cache.calculate_unrealized_pnl(position) - if unrealized_pnl is not None: - position_state["unrealized_pnl"] = str(unrealized_pnl) - self.kernel.msgbus.publish( - topic=f"snapshots:positions:{position.id}", - msg=self.kernel.msgbus.serializer.serialize(position_state), - ) - except asyncio.CancelledError: - pass - except Exception as e: - # Catch-all exceptions for development purposes (unexpected errors) - self.kernel.logger.error(str(e)) - def stop(self) -> None: """ Stop the trading node gracefully. @@ -440,20 +395,15 @@ async def stop_async(self) -> None: """ if self._task_streaming: - self.kernel.logger.info("Cancelling `task_streaming`") + self.kernel.logger.info("Canceling `task_streaming`") self._task_streaming.cancel() self._task_streaming = None if self._task_heartbeats: - self.kernel.logger.info("Cancelling `task_heartbeats`") + self.kernel.logger.info("Canceling `task_heartbeats`") self._task_heartbeats.cancel() self._task_heartbeats = None - if self._task_position_snapshots: - self.kernel.logger.info("Cancelling `task_position_snapshots`") - self._task_position_snapshots.cancel() - self._task_position_snapshots = None - await self.kernel.stop_async() self._is_running = False diff --git a/nautilus_trader/system/config.py b/nautilus_trader/system/config.py index e177adcdf0bf..4e107f8bb6ad 100644 --- a/nautilus_trader/system/config.py +++ b/nautilus_trader/system/config.py @@ -77,19 +77,6 @@ class NautilusKernelConfig(NautilusConfig, frozen=True): If the asyncio event loop should be in debug mode. logging : LoggingConfig, optional The logging config for the kernel. - snapshot_orders : bool, default False - If order state snapshot lists should be persisted. - Snapshots will be taken at every order state update (when events are applied). - snapshot_positions : bool, default False - If position state snapshot lists should be persisted. - Snapshots will be taken at position opened, changed and closed (when events are applied). - To include the unrealized PnL in the snapshot then quotes for the positions instrument must - be available in the cache. - snapshot_positions_interval : PositiveFloat, optional - The interval (seconds) at which additional position state snapshots are persisted. - If ``None`` then no additional snapshots will be taken. - To include the unrealized PnL in the snapshot then quotes for the positions instrument must - be available in the cache. timeout_connection : PositiveFloat (seconds) The timeout for all clients to connect and initialize. timeout_reconciliation : PositiveFloat (seconds) @@ -122,9 +109,7 @@ class NautilusKernelConfig(NautilusConfig, frozen=True): save_state: bool = False loop_debug: bool = False logging: LoggingConfig | None = None - snapshot_orders: bool = False - snapshot_positions: bool = False - snapshot_positions_interval: PositiveFloat | None = None + timeout_connection: PositiveFloat = 10.0 timeout_reconciliation: PositiveFloat = 10.0 timeout_portfolio: PositiveFloat = 10.0 diff --git a/nautilus_trader/system/kernel.py b/nautilus_trader/system/kernel.py index 58c949042077..f46e089c92ad 100644 --- a/nautilus_trader/system/kernel.py +++ b/nautilus_trader/system/kernel.py @@ -300,15 +300,11 @@ def __init__( # noqa (too complex) clock=self._clock, serializer=self._msgbus_serializer, database=self._msgbus_db, - snapshot_orders=config.snapshot_orders, - snapshot_positions=config.snapshot_positions, config=config.message_bus, ) self._cache = Cache( database=cache_db, - snapshot_orders=config.snapshot_orders, - snapshot_positions=config.snapshot_positions, config=config.cache, ) diff --git a/tests/unit_tests/backtest/test_config.py b/tests/unit_tests/backtest/test_config.py index 75540a0e61ed..12b75b5272c1 100644 --- a/tests/unit_tests/backtest/test_config.py +++ b/tests/unit_tests/backtest/test_config.py @@ -285,7 +285,7 @@ def test_backtest_run_config_id(self) -> None: TestConfigStubs.backtest_engine_config, ("catalog",), {"persist": True}, - ("7ec0724f21fc85299dc336ceb0c46795fbf2151a1f239ff794930861449563a6",), + ("eec4a983b988d398cafeaf45e3eec4bf2ac12bbaa840442b8a1174da2ca0b9f5",), ), ( TestConfigStubs.risk_engine_config, @@ -297,7 +297,7 @@ def test_backtest_run_config_id(self) -> None: TestConfigStubs.exec_engine_config, (), {}, - ("33901383a61bc99b14f5f02de3735bcf8b287243de55ce330d32c3ade274d8e0",), + ("3c10dbf0e37728807d5b015505c1d978f1d6c1555318cdb040a2aa950a95f554",), ), ( TestConfigStubs.streaming_config, From aaeaa4f9838c2490cba28619a487cc769b354d23 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 13:20:32 +1000 Subject: [PATCH 59/72] Improve logging --- nautilus_trader/adapters/binance/futures/execution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nautilus_trader/adapters/binance/futures/execution.py b/nautilus_trader/adapters/binance/futures/execution.py index e8bb4e789f7b..3ec4ccebaeb4 100644 --- a/nautilus_trader/adapters/binance/futures/execution.py +++ b/nautilus_trader/adapters/binance/futures/execution.py @@ -181,7 +181,7 @@ async def _init_dual_side_position(self) -> None: self._use_reduce_only, "Cannot use `reduce_only` with Binance Hedge Mode", ) - self._log.info(f"Dual side position: {self._is_dual_side_position}") + self._log.info(f"Dual side position: {self._is_dual_side_position}", LogColor.BLUE) # -- EXECUTION REPORTS ------------------------------------------------------------------------ From fe98c7522c700da09cd07172823c8c40ab017119 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 13:24:44 +1000 Subject: [PATCH 60/72] Improve logging --- nautilus_trader/live/execution_engine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nautilus_trader/live/execution_engine.py b/nautilus_trader/live/execution_engine.py index ffef60b8f4e2..9b857a2f27de 100644 --- a/nautilus_trader/live/execution_engine.py +++ b/nautilus_trader/live/execution_engine.py @@ -546,7 +546,7 @@ def reconcile_report(self, report: ExecutionReport) -> bool: True if reconciliation successful, else False. """ - self._log.debug(f"[RECV][RPT] {report}") + self._log.debug(f"<--[RPT] {report}") self.report_count += 1 self._log.info(f"Reconciling {report}", color=LogColor.BLUE) @@ -588,7 +588,7 @@ def _reconcile_mass_status( self, mass_status: ExecutionMassStatus, ) -> bool: - self._log.debug(f"[RECV][RPT] {mass_status}") + self._log.debug(f"<--[RPT] {mass_status}") self.report_count += 1 self._log.info( From 3dc28b41cdbc5a1663e87ec8828dba23847f2f61 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 13:36:53 +1000 Subject: [PATCH 61/72] Refine order state and position state snapshotting --- .../binance_futures_testnet_ema_cross.py | 21 +++++ nautilus_trader/cache/cache.pxd | 2 +- nautilus_trader/cache/cache.pyx | 5 +- nautilus_trader/execution/engine.pxd | 7 +- nautilus_trader/execution/engine.pyx | 88 ++++++++++--------- 5 files changed, 75 insertions(+), 48 deletions(-) diff --git a/examples/live/binance/binance_futures_testnet_ema_cross.py b/examples/live/binance/binance_futures_testnet_ema_cross.py index 77a1e470ce95..cba303554912 100644 --- a/examples/live/binance/binance_futures_testnet_ema_cross.py +++ b/examples/live/binance/binance_futures_testnet_ema_cross.py @@ -21,6 +21,7 @@ from nautilus_trader.adapters.binance.config import BinanceExecClientConfig from nautilus_trader.adapters.binance.factories import BinanceLiveDataClientFactory from nautilus_trader.adapters.binance.factories import BinanceLiveExecClientFactory +from nautilus_trader.cache.config import CacheConfig from nautilus_trader.config import InstrumentProviderConfig from nautilus_trader.config import LiveExecEngineConfig from nautilus_trader.config import LoggingConfig @@ -42,9 +43,27 @@ trader_id=TraderId("TESTER-001"), logging=LoggingConfig(log_level="INFO"), exec_engine=LiveExecEngineConfig( + # debug=True, reconciliation=True, reconciliation_lookback_mins=1440, + # snapshot_orders=True, + # snapshot_positions=True, + # snapshot_positions_interval_secs=5.0, ), + cache=CacheConfig( + # database=DatabaseConfig(timeout=2), + timestamps_as_iso8601=True, + flush_on_start=False, + ), + # message_bus=MessageBusConfig( + # database=DatabaseConfig(timeout=2), + # timestamps_as_iso8601=True, + # use_instance_id=False, + # # types_filter=[QuoteTick], + # stream_per_topic=False, + # external_streams=["bybit"], + # autotrim_mins=30, + # ), data_clients={ "BINANCE": BinanceDataClientConfig( api_key=None, # 'BINANCE_API_KEY' env var @@ -90,6 +109,8 @@ trade_size=Decimal("0.010"), order_id_tag="001", oms_type="HEDGING", + subscribe_trade_ticks=True, + subscribe_quote_ticks=True, ) # Instantiate your strategy diff --git a/nautilus_trader/cache/cache.pxd b/nautilus_trader/cache/cache.pxd index 62a5a219cb90..7fb411c0066c 100644 --- a/nautilus_trader/cache/cache.pxd +++ b/nautilus_trader/cache/cache.pxd @@ -166,7 +166,7 @@ cdef class Cache(CacheFacade): cpdef void add_position_id(self, PositionId position_id, Venue venue, ClientOrderId client_order_id, StrategyId strategy_id) cpdef void add_position(self, Position position, OmsType oms_type) cpdef void snapshot_position(self, Position position) - cpdef void snapshot_position_state(self, Position position, uint64_t ts_snapshot, bint open_only=*) + cpdef void snapshot_position_state(self, Position position, uint64_t ts_snapshot, Money unrealized_pnl=*, bint open_only=*) cpdef void snapshot_order_state(self, Order order) cpdef void update_account(self, Account account) diff --git a/nautilus_trader/cache/cache.pyx b/nautilus_trader/cache/cache.pyx index 75b06f344000..aeeb66df6e14 100644 --- a/nautilus_trader/cache/cache.pyx +++ b/nautilus_trader/cache/cache.pyx @@ -1725,6 +1725,7 @@ cdef class Cache(CacheFacade): self, Position position, uint64_t ts_snapshot, + Money unrealized_pnl=None, bint open_only=True, ): """ @@ -1738,6 +1739,8 @@ cdef class Cache(CacheFacade): The position to snapshot the state for. ts_snapshot : uint64_t UNIX timestamp (nanoseconds) when the snapshot was taken. + unrealized_pnl : Money, optional + The current unrealized PnL for the position. open_only : bool, default True If only open positions should be snapshot, this flag helps to avoid race conditions where a position is snapshot when no longer open. @@ -1757,7 +1760,7 @@ cdef class Cache(CacheFacade): self._database.snapshot_position_state( position, ts_snapshot, - self.calculate_unrealized_pnl(position), + unrealized_pnl, ) cpdef void snapshot_order_state(self, Order order): diff --git a/nautilus_trader/execution/engine.pxd b/nautilus_trader/execution/engine.pxd index dc644c134b43..58fbf786c635 100644 --- a/nautilus_trader/execution/engine.pxd +++ b/nautilus_trader/execution/engine.pxd @@ -15,6 +15,7 @@ from nautilus_trader.cache.cache cimport Cache from nautilus_trader.common.component cimport Component +from nautilus_trader.common.component cimport TimeEvent from nautilus_trader.common.generators cimport PositionIdGenerator from nautilus_trader.core.rust.model cimport OmsType from nautilus_trader.core.rust.model cimport OrderSide @@ -127,6 +128,6 @@ cdef class ExecutionEngine(Component): cpdef void _update_position(self, Instrument instrument, Position position, OrderFilled fill, OmsType oms_type) cpdef bint _will_flip_position(self, Position position, OrderFilled fill) cpdef void _flip_position(self, Instrument instrument, Position position, OrderFilled fill, OmsType oms_type) - cpdef void _publish_order_snapshot(self, Order order) - cpdef void _publish_position_snapshot(self, Position position) - cpdef void _snapshot_open_positions(self) + cpdef void _create_order_state_snapshot(self, Order order) + cpdef void _create_position_state_snapshot(self, Position position) + cpdef void _snapshot_open_position_states(self, TimeEvent event) diff --git a/nautilus_trader/execution/engine.pyx b/nautilus_trader/execution/engine.pyx index 06cd28d4c3ce..774ae1b0daba 100644 --- a/nautilus_trader/execution/engine.pyx +++ b/nautilus_trader/execution/engine.pyx @@ -49,6 +49,7 @@ from nautilus_trader.common.component cimport Component from nautilus_trader.common.component cimport LogColor from nautilus_trader.common.component cimport Logger from nautilus_trader.common.component cimport MessageBus +from nautilus_trader.common.component cimport TimeEvent from nautilus_trader.common.generators cimport PositionIdGenerator from nautilus_trader.core.correctness cimport Condition from nautilus_trader.core.fsm cimport InvalidStateTrigger @@ -151,6 +152,10 @@ cdef class ExecutionEngine(Component): self.snapshot_positions_interval_secs = config.snapshot_positions_interval_secs or 0 self.snapshot_positions_timer_name = "ExecEngine_SNAPSHOT_POSITIONS" + self._log.info(f"{config.snapshot_orders=}", LogColor.BLUE) + self._log.info(f"{config.snapshot_positions=}", LogColor.BLUE) + self._log.info(f"{config.snapshot_positions_interval_secs=}", LogColor.BLUE) + # Counters self.command_count: int = 0 self.event_count: int = 0 @@ -551,7 +556,7 @@ cdef class ExecutionEngine(Component): interval_ns=interval_ns, start_time_ns=0, # TBD if should align to nearest second boundary stop_time_ns=0, # Run as long as execution engine is running - callback=self._snapshot_open_positions, + callback=self._snapshot_open_position_states, ) self._on_start() @@ -733,8 +738,8 @@ cdef class ExecutionEngine(Component): topic=f"events.order.{order.strategy_id}", msg=denied, ) - if self.snapshot_orders and (self._cache.has_backing or self._msgbus.has_backing): - self._publish_order_snapshot(order) + if self.snapshot_orders: + self._create_order_state_snapshot(order) # -- COMMAND HANDLERS ----------------------------------------------------------------------------- @@ -781,8 +786,8 @@ cdef class ExecutionEngine(Component): if not self._cache.order_exists(order.client_order_id): # Cache order self._cache.add_order(order, command.position_id, command.client_id) - if self.snapshot_orders and (self._cache.has_backing or self._msgbus.has_backing): - self._publish_order_snapshot(order) + if self.snapshot_orders: + self._create_order_state_snapshot(order) cdef Instrument instrument = self._cache.instrument(order.instrument_id) if instrument is None: @@ -812,8 +817,8 @@ cdef class ExecutionEngine(Component): if not self._cache.order_exists(order.client_order_id): # Cache order self._cache.add_order(order, command.position_id, command.client_id) - if self.snapshot_orders and (self._cache.has_backing or self._msgbus.has_backing): - self._publish_order_snapshot(order) + if self.snapshot_orders: + self._create_order_state_snapshot(order) cdef Instrument instrument = self._cache.instrument(command.instrument_id) if instrument is None: @@ -1041,8 +1046,8 @@ cdef class ExecutionEngine(Component): topic=f"events.order.{event.strategy_id}", msg=event, ) - if self.snapshot_orders and (self._cache.has_backing or self._msgbus.has_backing): - self._publish_order_snapshot(order) + if self.snapshot_orders: + self._create_order_state_snapshot(order) cpdef void _handle_order_fill(self, Order order, OrderFilled fill, OmsType oms_type): cdef Instrument instrument = self._cache.load_instrument(fill.instrument_id) @@ -1088,8 +1093,8 @@ cdef class ExecutionEngine(Component): if position is None: position = Position(instrument, fill) self._cache.add_position(position, oms_type) - if self.snapshot_positions and (self._cache.has_backing or self._msgbus.has_backing): - self._publish_position_snapshot(position) + if self.snapshot_positions: + self._create_position_state_snapshot(position) else: try: # Always snapshot opening positions to handle NETTING OMS @@ -1124,8 +1129,8 @@ cdef class ExecutionEngine(Component): return # Not re-raising to avoid crashing engine self._cache.update_position(position) - if self.snapshot_positions and (self._cache.has_backing or self._msgbus.has_backing): - self._publish_position_snapshot(position) + if self.snapshot_positions: + self._create_position_state_snapshot(position) cdef PositionEvent event if position.is_closed_c(): @@ -1241,30 +1246,24 @@ cdef class ExecutionEngine(Component): # Open flipped position self._open_position(instrument, None, fill_split2, oms_type) - cpdef void _publish_order_snapshot(self, Order order): + cpdef void _create_order_state_snapshot(self, Order order): + if self.debug: + self._log.debug(f"Creating order state snapshot for {order}", LogColor.MAGENTA) + if self._cache.has_backing: - # Persist position snapshot to database self._cache.snapshot_order_state(order) - if self._msgbus.has_backing: - # Publish order snapshot on message bus - if self._msgbus.serializer is not None: - self._msgbus.publish_c( - topic=f"snapshots:orders:{order.client_order_id.to_str()}", - msg=self._msgbus.serializer.serialize(order.to_dict()) - ) - - cpdef void _publish_position_snapshot(self, Position position): - cdef Money unrealized_pnl = self._cache.calculate_unrealized_pnl(position) - - if self._cache.has_backing: - # Persist position snapshot to database - self._cache.snapshot_position_state( - position, - position.ts_last, - unrealized_pnl, + if self._msgbus.has_backing and self._msgbus.serializer is not None: + self._msgbus.publish_c( + topic=f"snapshots:orders:{order.client_order_id.to_str()}", + msg=self._msgbus.serializer.serialize(order.to_dict()) ) + cpdef void _create_position_state_snapshot(self, Position position): + if self.debug: + self._log.debug(f"Creating position state snapshot for {position}", LogColor.MAGENTA) + + cdef Money unrealized_pnl = self._cache.calculate_unrealized_pnl(position) cdef dict[str, object] position_state = position.to_dict() if unrealized_pnl is not None: position_state["unrealized_pnl"] = str(unrealized_pnl) @@ -1276,17 +1275,20 @@ cdef class ExecutionEngine(Component): external_pub=False, ) - if self._msgbus.has_backing: - # Publish position snapshot on message bus - if self._msgbus.serializer is not None: - self._msgbus.publish_c( - topic=f"snapshots:positions:{position.id}", - msg=self._msgbus.serializer.serialize(position_state), - ) + if self._cache.has_backing: + self._cache.snapshot_position_state( + position, + position.ts_last, + unrealized_pnl, + ) - cpdef void _snapshot_open_positions(self): - cdef list[Position] open_positions = self._cache.positions_open() + if self._msgbus.has_backing and self._msgbus.serializer is not None: + self._msgbus.publish_c( + topic=f"snapshots:positions:{position.id}", + msg=self._msgbus.serializer.serialize(position_state), + ) + cpdef void _snapshot_open_position_states(self, TimeEvent event): cdef Position position - for position in open_positions: - self._publish_position_snapshot(position) + for position in self._cache.positions_open(): + self._create_position_state_snapshot(position) From 0d62024ae2693de52bc05c4fe26ae9e5861f421c Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 14:44:01 +1000 Subject: [PATCH 62/72] Remove sleeps from some Binance integration tests --- .../adapters/binance/test_data_spot.py | 14 +++++++------- .../binance/test_execution_futures.py | 19 ++++++++++--------- .../adapters/binance/test_execution_spot.py | 15 +++++++-------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/integration_tests/adapters/binance/test_data_spot.py b/tests/integration_tests/adapters/binance/test_data_spot.py index 78ed8c409517..d661c58b543d 100644 --- a/tests/integration_tests/adapters/binance/test_data_spot.py +++ b/tests/integration_tests/adapters/binance/test_data_spot.py @@ -35,6 +35,7 @@ from nautilus_trader.model.identifiers import TradeId from nautilus_trader.model.objects import Price from nautilus_trader.model.objects import Quantity +from nautilus_trader.test_kit.functions import eventually from nautilus_trader.test_kit.providers import TestInstrumentProvider from nautilus_trader.test_kit.stubs.component import TestComponentStubs from nautilus_trader.test_kit.stubs.identifiers import TestIdStubs @@ -123,10 +124,9 @@ async def mock_send_request( # Act self.data_client.connect() - await asyncio.sleep(1) # Assert - assert self.data_client.is_connected + await eventually(lambda: self.data_client.is_connected) @pytest.mark.asyncio() async def test_disconnect(self, monkeypatch): @@ -201,7 +201,7 @@ async def mock_send_request( ) self.data_client.connect() - await asyncio.sleep(1) + await eventually(lambda: self.data_client.is_connected) # Act self.data_client.subscribe_instruments() @@ -243,7 +243,7 @@ async def mock_send_request( ) self.data_client.connect() - await asyncio.sleep(1) + await eventually(lambda: self.data_client.is_connected) ethusdt = InstrumentId.from_str("ETHUSDT.BINANCE") @@ -271,7 +271,7 @@ async def test_subscribe_quote_ticks(self, monkeypatch): # Assert self.data_client._handle_ws_message(raw_book_tick) - await asyncio.sleep(1) + await eventually(lambda: self.data_engine.data_count) assert self.data_engine.data_count == 1 assert len(handler) == 1 # <-- handler received tick @@ -303,7 +303,7 @@ async def test_subscribe_trade_ticks(self, monkeypatch): # Assert self.data_client._handle_ws_message(raw_trade) - await asyncio.sleep(1) + await eventually(lambda: self.data_engine.data_count) assert self.data_engine.data_count == 1 assert len(handler) == 1 # <-- handler received tick @@ -337,7 +337,7 @@ async def test_subscribe_agg_trade_ticks(self, monkeypatch): # Assert self.data_client._handle_ws_message(raw_trade) - await asyncio.sleep(1) + await eventually(lambda: self.data_engine.data_count) assert self.data_engine.data_count == 1 assert len(handler) == 1 # <-- handler received tick diff --git a/tests/integration_tests/adapters/binance/test_execution_futures.py b/tests/integration_tests/adapters/binance/test_execution_futures.py index 58f2a8ee3ea1..0a59b600d576 100644 --- a/tests/integration_tests/adapters/binance/test_execution_futures.py +++ b/tests/integration_tests/adapters/binance/test_execution_futures.py @@ -40,6 +40,7 @@ from nautilus_trader.model.objects import Quantity from nautilus_trader.portfolio.portfolio import Portfolio from nautilus_trader.risk.engine import RiskEngine +from nautilus_trader.test_kit.functions import eventually from nautilus_trader.test_kit.providers import TestInstrumentProvider from nautilus_trader.test_kit.stubs.component import TestComponentStubs from nautilus_trader.test_kit.stubs.identifiers import TestIdStubs @@ -166,10 +167,10 @@ async def test_submit_market_order(self, mocker, _is_dual_side_position, positio command_id=UUID4(), ts_init=0, ) - print(f"submit_order: {submit_order}") + # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -225,7 +226,7 @@ async def test_submit_limit_order(self, mocker, _is_dual_side_position, position # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -289,7 +290,7 @@ async def test_submit_limit_post_only_order( # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -356,7 +357,7 @@ async def test_submit_stop_market_order( # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -428,7 +429,7 @@ async def test_submit_stop_limit_order( # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -495,7 +496,7 @@ async def test_submit_market_if_touched_order( # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -561,7 +562,7 @@ async def test_submit_limit_if_touched_order( # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -633,7 +634,7 @@ async def test_trailing_stop_market_order( # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args diff --git a/tests/integration_tests/adapters/binance/test_execution_spot.py b/tests/integration_tests/adapters/binance/test_execution_spot.py index 96d893697844..d7079c85c094 100644 --- a/tests/integration_tests/adapters/binance/test_execution_spot.py +++ b/tests/integration_tests/adapters/binance/test_execution_spot.py @@ -40,6 +40,7 @@ from nautilus_trader.model.objects import Quantity from nautilus_trader.portfolio.portfolio import Portfolio from nautilus_trader.risk.engine import RiskEngine +from nautilus_trader.test_kit.functions import eventually from nautilus_trader.test_kit.providers import TestInstrumentProvider from nautilus_trader.test_kit.stubs.component import TestComponentStubs from nautilus_trader.test_kit.stubs.identifiers import TestIdStubs @@ -187,10 +188,9 @@ async def mock_ws_connect( # Act self.exec_client.connect() - await asyncio.sleep(1) # Assert - assert self.exec_client.is_connected + await eventually(lambda: self.exec_client.is_connected) @pytest.mark.asyncio() async def test_submit_unsupported_order_logs_error(self, mocker): @@ -247,7 +247,7 @@ async def test_submit_market_order(self, mocker): # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -286,7 +286,7 @@ async def test_submit_limit_order(self, mocker): # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -327,7 +327,7 @@ async def test_submit_stop_limit_order(self, mocker): # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -371,7 +371,7 @@ async def test_submit_limit_if_touched_order(self, mocker): # Act self.exec_client.submit_order(submit_order) - await asyncio.sleep(0.3) + await eventually(lambda: mock_send_request.call_args) # Assert request = mock_send_request.call_args @@ -404,7 +404,6 @@ async def test_query_order(self, mocker): # Act self.strategy.query_order(order) - await asyncio.sleep(0.3) # Assert - assert mock_query_order.called + await eventually(lambda: mock_query_order.called) From d9e07b2e434137cc98287cb2b0e3fc27067415c1 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 15:12:51 +1000 Subject: [PATCH 63/72] Standardize task logging --- nautilus_core/network/src/websocket.rs | 4 ++-- nautilus_trader/adapters/betfair/factories.py | 4 ++-- nautilus_trader/adapters/betfair/providers.py | 2 +- nautilus_trader/adapters/binance/common/data.py | 4 ++-- .../adapters/binance/common/execution.py | 6 +++--- .../adapters/binance/spot/execution.py | 2 +- nautilus_trader/adapters/bybit/data.py | 6 +++--- nautilus_trader/adapters/databento/data.py | 8 ++++---- .../interactive_brokers/client/client.py | 6 +++--- nautilus_trader/live/data_client.py | 12 ++++++------ nautilus_trader/live/data_engine.py | 16 ++++++++-------- nautilus_trader/live/execution_client.py | 6 +++--- nautilus_trader/live/execution_engine.py | 12 ++++++------ nautilus_trader/live/node.py | 4 ++-- nautilus_trader/live/risk_engine.py | 8 ++++---- nautilus_trader/system/kernel.py | 2 +- 16 files changed, 51 insertions(+), 51 deletions(-) diff --git a/nautilus_core/network/src/websocket.rs b/nautilus_core/network/src/websocket.rs index 5d72b2a8057a..20a75739b26b 100644 --- a/nautilus_core/network/src/websocket.rs +++ b/nautilus_core/network/src/websocket.rs @@ -132,7 +132,7 @@ impl WebSocketClientInner { message: Option, writer: SharedMessageWriter, ) -> Option> { - tracing::debug!("Started task `heartbeat`"); + tracing::debug!("Started task 'heartbeat'"); heartbeat.map(|duration| { task::spawn(async move { let duration = Duration::from_secs(duration); @@ -158,7 +158,7 @@ impl WebSocketClientInner { handler: PyObject, ping_handler: Option, ) -> task::JoinHandle<()> { - tracing::debug!("Started task `read`"); + tracing::debug!("Started task 'read'"); task::spawn(async move { loop { match reader.next().await { diff --git a/nautilus_trader/adapters/betfair/factories.py b/nautilus_trader/adapters/betfair/factories.py index 7919ac430961..b2832582a7d5 100644 --- a/nautilus_trader/adapters/betfair/factories.py +++ b/nautilus_trader/adapters/betfair/factories.py @@ -74,7 +74,7 @@ def get_cached_betfair_client( key: str = "|".join((username, password, app_key)) if key not in CLIENTS: - Logger("BetfairFactory").warning("Creating new instance of BetfairHttpClient") + Logger("BetfairFactory").warning("Creating new instance of `BetfairHttpClient`") client = BetfairHttpClient( username=username, @@ -109,7 +109,7 @@ def get_cached_betfair_instrument_provider( """ global INSTRUMENT_PROVIDER if INSTRUMENT_PROVIDER is None: - Logger("BetfairFactory").warning("Creating new instance of BetfairInstrumentProvider") + Logger("BetfairFactory").warning("Creating new instance of `BetfairInstrumentProvider`") INSTRUMENT_PROVIDER = BetfairInstrumentProvider( client=client, diff --git a/nautilus_trader/adapters/betfair/providers.py b/nautilus_trader/adapters/betfair/providers.py index 1ef99176a0fb..d7f2b03c9175 100644 --- a/nautilus_trader/adapters/betfair/providers.py +++ b/nautilus_trader/adapters/betfair/providers.py @@ -107,7 +107,7 @@ async def load_all_async(self, filters: dict | None = None): self._log.info(f"Found {len(markets)} markets, loading metadata") market_metadata = await load_markets_metadata(client=self._client, markets=markets) - self._log.info("Creating instruments..") + self._log.info("Creating instruments...") instruments = [ instrument for metadata in market_metadata diff --git a/nautilus_trader/adapters/binance/common/data.py b/nautilus_trader/adapters/binance/common/data.py index 7f9b7298481b..055771cf86ed 100644 --- a/nautilus_trader/adapters/binance/common/data.py +++ b/nautilus_trader/adapters/binance/common/data.py @@ -244,7 +244,7 @@ async def _update_instruments(self) -> None: ) await asyncio.sleep(self._retry_delay) except asyncio.CancelledError: - self._log.debug("Canceled `update_instruments` task") + self._log.debug("Canceled task 'update_instruments'") return async def _reconnect(self) -> None: @@ -257,7 +257,7 @@ async def _reconnect(self) -> None: async def _disconnect(self) -> None: # Cancel update instruments task if self._update_instruments_task: - self._log.debug("Canceling `update_instruments` task") + self._log.debug("Canceling task 'update_instruments'") self._update_instruments_task.cancel() self._update_instruments_task = None diff --git a/nautilus_trader/adapters/binance/common/execution.py b/nautilus_trader/adapters/binance/common/execution.py index 09d77a018afa..9a414614776d 100644 --- a/nautilus_trader/adapters/binance/common/execution.py +++ b/nautilus_trader/adapters/binance/common/execution.py @@ -302,7 +302,7 @@ async def _ping_listen_keys(self) -> None: try: while True: self._log.debug( - f"Scheduled `ping_listen_keys` to run in " + f"Scheduled task 'ping_listen_keys' to run in " f"{self._ping_listen_keys_interval}s", ) await asyncio.sleep(self._ping_listen_keys_interval) @@ -314,12 +314,12 @@ async def _ping_listen_keys(self) -> None: # We may see this if an old listen key was used for the ping self._log.error(f"Error pinging listen key: {e}") except asyncio.CancelledError: - self._log.debug("Canceled `ping_listen_keys` task") + self._log.debug("Canceled task 'ping_listen_keys'") async def _disconnect(self) -> None: # Cancel tasks if self._ping_listen_keys_task: - self._log.debug("Canceling `ping_listen_keys` task") + self._log.debug("Canceling task 'ping_listen_keys'") self._ping_listen_keys_task.cancel() self._ping_listen_keys_task = None diff --git a/nautilus_trader/adapters/binance/spot/execution.py b/nautilus_trader/adapters/binance/spot/execution.py index 7d5ca0cf4097..3a1fee09370d 100644 --- a/nautilus_trader/adapters/binance/spot/execution.py +++ b/nautilus_trader/adapters/binance/spot/execution.py @@ -157,7 +157,7 @@ async def _update_account_state(self) -> None: async def _init_dual_side_position(self) -> None: self._is_dual_side_position = False - self._log.info(f"Dual side position: {self._is_dual_side_position}") + self._log.info(f"Dual side position: {self._is_dual_side_position}", LogColor.BLUE) # -- EXECUTION REPORTS ------------------------------------------------------------------------ diff --git a/nautilus_trader/adapters/bybit/data.py b/nautilus_trader/adapters/bybit/data.py index 295c0e3d0342..e1a8f472afc7 100644 --- a/nautilus_trader/adapters/bybit/data.py +++ b/nautilus_trader/adapters/bybit/data.py @@ -222,7 +222,7 @@ async def _connect(self) -> None: async def _disconnect(self) -> None: if self._update_instruments_task: - self._log.debug("Canceling `update_instruments` task") + self._log.debug("Canceling task 'update_instruments'") self._update_instruments_task.cancel() self._update_instruments_task = None for ws_client in self._ws_clients.values(): @@ -239,14 +239,14 @@ async def _update_instruments(self) -> None: try: while True: self._log.debug( - f"Scheduled `update_instruments` to run in " + f"Scheduled task 'update_instruments' to run in " f"{self._update_instrument_interval}s", ) await asyncio.sleep(self._update_instrument_interval) await self._instrument_provider.load_all_async() self._send_all_instruments_to_data_engine() except asyncio.CancelledError: - self._log.debug("Canceled `update_instruments` task") + self._log.debug("Canceled task 'update_instruments'") async def _subscribe_order_book_deltas( self, diff --git a/nautilus_trader/adapters/databento/data.py b/nautilus_trader/adapters/databento/data.py index bb7a9a59f953..7f8d03d6b1aa 100644 --- a/nautilus_trader/adapters/databento/data.py +++ b/nautilus_trader/adapters/databento/data.py @@ -181,13 +181,13 @@ async def _connect(self) -> None: async def _disconnect(self) -> None: if self._buffer_mbo_subscriptions_task: - self._log.debug("Canceling `buffer_mbo_subscriptions` task") + self._log.debug("Canceling task 'buffer_mbo_subscriptions'") self._buffer_mbo_subscriptions_task.cancel() self._buffer_mbo_subscriptions_task = None # Cancel update dataset ranges task if self._update_dataset_ranges_task: - self._log.debug("Canceling `update_dataset_ranges` task") + self._log.debug("Canceling task 'update_dataset_ranges'") self._update_dataset_ranges_task.cancel() self._update_dataset_ranges_task = None @@ -227,7 +227,7 @@ async def _update_dataset_ranges(self) -> None: except Exception as e: # Create specific exception type self._log.error(f"Error updating dataset range: {e}") except asyncio.CancelledError: - self._log.debug("Canceled `update_dataset_ranges` task") + self._log.debug("Canceled task 'update_dataset_ranges'") break async def _buffer_mbo_subscriptions(self) -> None: @@ -244,7 +244,7 @@ async def _buffer_mbo_subscriptions(self) -> None: await asyncio.gather(*coros) except asyncio.CancelledError: - self._log.debug("Canceled `buffer_mbo_subscriptions` task") + self._log.debug("Canceled task 'buffer_mbo_subscriptions'") def _get_live_client(self, dataset: Dataset) -> nautilus_pyo3.DatabentoLiveClient: # Retrieve or initialize the 'general' live client for the specified dataset diff --git a/nautilus_trader/adapters/interactive_brokers/client/client.py b/nautilus_trader/adapters/interactive_brokers/client/client.py index 63573eea3bd8..481eb2216f2c 100644 --- a/nautilus_trader/adapters/interactive_brokers/client/client.py +++ b/nautilus_trader/adapters/interactive_brokers/client/client.py @@ -398,7 +398,7 @@ def _create_task( """ log_msg = log_msg or coro.__name__ - self._log.debug(f"Creating task {log_msg}.") + self._log.debug(f"Creating task '{log_msg}'") task = self._loop.create_task( coro, name=coro.__name__, @@ -433,7 +433,7 @@ def _on_task_completed( """ if task.exception(): self._log.error( - f"Error on `{task.get_name()}`: {task.exception()!r}", + f"Error on '{task.get_name()}': {task.exception()!r}", ) else: if actions: @@ -441,7 +441,7 @@ def _on_task_completed( actions() except Exception as e: self._log.error( - f"Failed triggering action {actions.__name__} on `{task.get_name()}`: " + f"Failed triggering action {actions.__name__} on '{task.get_name()}': " f"{e!r}", ) if success: diff --git a/nautilus_trader/live/data_client.py b/nautilus_trader/live/data_client.py index eaabc9010252..e5bb3fdd2f51 100644 --- a/nautilus_trader/live/data_client.py +++ b/nautilus_trader/live/data_client.py @@ -146,7 +146,7 @@ def create_task( """ log_msg = log_msg or coro.__name__ - self._log.debug(f"Creating task {log_msg}") + self._log.debug(f"Creating task '{log_msg}'") task = self._loop.create_task( coro, name=coro.__name__, @@ -172,7 +172,7 @@ def _on_task_completed( if e: tb_str = "".join(traceback.format_exception(type(e), e, e.__traceback__)) self._log.error( - f"Error on `{task.get_name()}`: {task.exception()!r}\n{tb_str}", + f"Error on '{task.get_name()}': {task.exception()!r}\n{tb_str}", ) else: if actions: @@ -181,7 +181,7 @@ def _on_task_completed( except Exception as e: tb_str = "".join(traceback.format_exception(type(e), e, e.__traceback__)) self._log.error( - f"Failed triggering action {actions.__name__} on `{task.get_name()}`: " + f"Failed triggering action {actions.__name__} on '{task.get_name()}': " f"{e!r}\n{tb_str}", ) if success_msg: @@ -373,7 +373,7 @@ def create_task( """ log_msg = log_msg or coro.__name__ - self._log.debug(f"Creating task {log_msg}") + self._log.debug(f"Creating task '{log_msg}'") task = self._loop.create_task( coro, name=coro.__name__, @@ -399,7 +399,7 @@ def _on_task_completed( if e: tb_str = "".join(traceback.format_exception(type(e), e, e.__traceback__)) self._log.error( - f"Error on `{task.get_name()}`: {task.exception()!r}\n{tb_str}", + f"Error on '{task.get_name()}': {task.exception()!r}\n{tb_str}", ) else: if actions: @@ -408,7 +408,7 @@ def _on_task_completed( except Exception as e: tb_str = "".join(traceback.format_exception(type(e), e, e.__traceback__)) self._log.error( - f"Failed triggering action {actions.__name__} on `{task.get_name()}`: " + f"Failed triggering action {actions.__name__} on '{task.get_name()}': " f"{e!r}\n{tb_str}", ) if success_msg: diff --git a/nautilus_trader/live/data_engine.py b/nautilus_trader/live/data_engine.py index 4bae9257f098..8d05a5c04a63 100644 --- a/nautilus_trader/live/data_engine.py +++ b/nautilus_trader/live/data_engine.py @@ -208,19 +208,19 @@ def kill(self) -> None: self._kill = True self.stop() if self._cmd_queue_task: - self._log.debug(f"Canceling {self._cmd_queue_task.get_name()}") + self._log.debug(f"Canceling task '{self._cmd_queue_task.get_name()}'") self._cmd_queue_task.cancel() self._cmd_queue_task = None if self._req_queue_task: - self._log.debug(f"Canceling {self._req_queue_task.get_name()}") + self._log.debug(f"Canceling task '{self._req_queue_task.get_name()}'") self._req_queue_task.cancel() self._req_queue_task = None if self._res_queue_task: - self._log.debug(f"Canceling {self._res_queue_task.get_name()}") + self._log.debug(f"Canceling task '{self._res_queue_task.get_name()}'") self._res_queue_task.cancel() self._res_queue_task = None if self._data_queue_task: - self._log.debug(f"Canceling {self._data_queue_task.get_name()}") + self._log.debug(f"Canceling task '{self._data_queue_task.get_name()}'") self._data_queue_task.cancel() self._data_queue_task = None @@ -366,10 +366,10 @@ def _on_start(self) -> None: self._res_queue_task = self._loop.create_task(self._run_req_queue(), name="req_queue") self._data_queue_task = self._loop.create_task(self._run_data_queue(), name="data_queue") - self._log.debug(f"Scheduled {self._cmd_queue_task}") - self._log.debug(f"Scheduled {self._req_queue_task}") - self._log.debug(f"Scheduled {self._res_queue_task}") - self._log.debug(f"Scheduled {self._data_queue_task}") + self._log.debug(f"Scheduled task '{self._cmd_queue_task.get_name()}'") + self._log.debug(f"Scheduled task '{self._req_queue_task.get_name()}'") + self._log.debug(f"Scheduled task '{self._res_queue_task.get_name()}'") + self._log.debug(f"Scheduled task '{self._data_queue_task.get_name()}'") def _on_stop(self) -> None: if self._kill: diff --git a/nautilus_trader/live/execution_client.py b/nautilus_trader/live/execution_client.py index e09c75a84fc5..c4efbda0bba0 100644 --- a/nautilus_trader/live/execution_client.py +++ b/nautilus_trader/live/execution_client.py @@ -179,7 +179,7 @@ def create_task( """ log_msg = log_msg or coro.__name__ - self._log.debug(f"Creating task {log_msg}") + self._log.debug(f"Creating task '{log_msg}'") task = self._loop.create_task( coro, name=coro.__name__, @@ -205,7 +205,7 @@ def _on_task_completed( if e: tb_str = "".join(traceback.format_exception(type(e), e, e.__traceback__)) self._log.error( - f"Error on `{task.get_name()}`: {task.exception()!r}\n{tb_str}", + f"Error on '{task.get_name()}': {task.exception()!r}\n{tb_str}", ) else: if actions: @@ -214,7 +214,7 @@ def _on_task_completed( except Exception as e: tb_str = "".join(traceback.format_exception(type(e), e, e.__traceback__)) self._log.error( - f"Failed triggering action {actions.__name__} on `{task.get_name()}`: " + f"Failed triggering action {actions.__name__} on '{task.get_name()}': " f"{e!r}\n{tb_str}", ) if success_msg: diff --git a/nautilus_trader/live/execution_engine.py b/nautilus_trader/live/execution_engine.py index 9b857a2f27de..ebbd3d66f8a1 100644 --- a/nautilus_trader/live/execution_engine.py +++ b/nautilus_trader/live/execution_engine.py @@ -252,11 +252,11 @@ def kill(self) -> None: self._kill = True self.stop() if self._cmd_queue_task: - self._log.debug(f"Canceling {self._cmd_queue_task.get_name()}") + self._log.debug(f"Canceling task '{self._cmd_queue_task.get_name()}'") self._cmd_queue_task.cancel() self._cmd_queue_task = None if self._evt_queue_task: - self._log.debug(f"Canceling {self._evt_queue_task.get_name()}") + self._log.debug(f"Canceling task '{self._evt_queue_task.get_name()}'") self._evt_queue_task.cancel() self._evt_queue_task = None @@ -334,8 +334,8 @@ def _on_start(self) -> None: self._cmd_queue_task = self._loop.create_task(self._run_cmd_queue(), name="cmd_queue") self._evt_queue_task = self._loop.create_task(self._run_evt_queue(), name="evt_queue") - self._log.debug(f"Scheduled {self._cmd_queue_task}") - self._log.debug(f"Scheduled {self._evt_queue_task}") + self._log.debug(f"Scheduled task '{self._cmd_queue_task.get_name()}'") + self._log.debug(f"Scheduled task '{self._evt_queue_task.get_name()}'") if not self._inflight_check_task: if self.inflight_check_interval_ms > 0: @@ -343,11 +343,11 @@ def _on_start(self) -> None: self._inflight_check_loop(), name="inflight_check", ) - self._log.debug(f"Scheduled {self._inflight_check_task}") + self._log.debug(f"Scheduled task '{self._inflight_check_task.get_name()}'") def _on_stop(self) -> None: if self._inflight_check_task: - self._log.info("Canceling in-flight check task") + self._log.info(f"Canceling task '{self._inflight_check_task.get_name()}'") self._inflight_check_task.cancel() self._inflight_check_task = None diff --git a/nautilus_trader/live/node.py b/nautilus_trader/live/node.py index a194986015f0..7c8b36ab4540 100644 --- a/nautilus_trader/live/node.py +++ b/nautilus_trader/live/node.py @@ -395,12 +395,12 @@ async def stop_async(self) -> None: """ if self._task_streaming: - self.kernel.logger.info("Canceling `task_streaming`") + self.kernel.logger.info("Canceling task 'streaming'") self._task_streaming.cancel() self._task_streaming = None if self._task_heartbeats: - self.kernel.logger.info("Canceling `task_heartbeats`") + self.kernel.logger.info("Canceling task 'heartbeats'") self._task_heartbeats.cancel() self._task_heartbeats = None diff --git a/nautilus_trader/live/risk_engine.py b/nautilus_trader/live/risk_engine.py index 666230d634bd..6a14dfbfbc0c 100644 --- a/nautilus_trader/live/risk_engine.py +++ b/nautilus_trader/live/risk_engine.py @@ -139,11 +139,11 @@ def kill(self) -> None: self._kill = True self.stop() if self._cmd_queue_task: - self._log.debug(f"Canceling {self._cmd_queue_task.get_name()}") + self._log.debug(f"Canceling task '{self._cmd_queue_task.get_name()}'") self._cmd_queue_task.cancel() self._cmd_queue_task = None if self._evt_queue_task: - self._log.debug(f"Canceling {self._evt_queue_task.get_name()}") + self._log.debug(f"Canceling task '{self._evt_queue_task.get_name()}'") self._evt_queue_task.cancel() self._evt_queue_task = None @@ -223,8 +223,8 @@ def _on_start(self) -> None: self._cmd_queue_task = self._loop.create_task(self._run_cmd_queue(), name="cmd_queue") self._evt_queue_task = self._loop.create_task(self._run_evt_queue(), name="evt_queue") - self._log.debug(f"Scheduled {self._cmd_queue_task}") - self._log.debug(f"Scheduled {self._evt_queue_task}") + self._log.debug(f"Scheduled task '{self._cmd_queue_task.get_name()}'") + self._log.debug(f"Scheduled task '{self._evt_queue_task.get_name()}'") def _on_stop(self) -> None: if self._kill: diff --git a/nautilus_trader/system/kernel.py b/nautilus_trader/system/kernel.py index f46e089c92ad..06395e2dfbc5 100644 --- a/nautilus_trader/system/kernel.py +++ b/nautilus_trader/system/kernel.py @@ -996,7 +996,7 @@ def cancel_all_tasks(self) -> None: return for task in to_cancel: - self._log.warning(f"Canceling pending task {task}") + self._log.warning(f"Canceling pending task '{task.get_name()}'") task.cancel() if self.loop and self.loop.is_running(): From 19f8e27c7a09b91f76def894c8f3c15ea27f47fc Mon Sep 17 00:00:00 2001 From: DevRoss Date: Sun, 18 Aug 2024 14:13:26 +0800 Subject: [PATCH 64/72] Add docs for Binance hedge mode (#1857) --- docs/integrations/binance.md | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/docs/integrations/binance.md b/docs/integrations/binance.md index de50cd63ddaa..6c5f522b6e52 100644 --- a/docs/integrations/binance.md +++ b/docs/integrations/binance.md @@ -228,6 +228,72 @@ instrument_provider=InstrumentProviderConfig( ) ``` +### Futures Hedge mode +Binance Futures Hedge mode is a position mode where a trader opens positions in both long and short directions to mitigate risk and potentially profit from market volatility. Before starting a + +To use Binance Future Hedge mode, you need to follow the three items below: +1. Before starting the strategy, please ensure that hedge mode is configured on Binance. +2. Set the `use_reduce_only` option to `False` in BinanceExecClientConfig (this is `True` by default.) + ```python + config = TradingNodeConfig( + ..., # Omitted + data_clients={ + "BINANCE": BinanceDataClientConfig( + api_key=None, # 'BINANCE_API_KEY' env var + api_secret=None, # 'BINANCE_API_SECRET' env var + account_type=BinanceAccountType.USDT_FUTURE, + base_url_http=None, # Override with custom endpoint + base_url_ws=None, # Override with custom endpoint + ), + }, + exec_clients={ + "BINANCE": BinanceExecClientConfig( + api_key=None, # 'BINANCE_API_KEY' env var + api_secret=None, # 'BINANCE_API_SECRET' env var + account_type=BinanceAccountType.USDT_FUTURE, + base_url_http=None, # Override with custom endpoint + base_url_ws=None, # Override with custom endpoint + use_reduce_only=False, # Must be disabled for Hedge mode + ), + } + ) + ``` + + +3. When submitting an order, use a suffix (`LONG` or `SHORT` ) in the position_id to indicate the position direction. + ```python + class EMACrossHedgeMode(Strategy): + ..., # Omitted + def buy(self) -> None: + """ + Users simple buy method (example). + """ + order: MarketOrder = self.order_factory.market( + instrument_id=self.instrument_id, + order_side=OrderSide.BUY, + quantity=self.instrument.make_qty(self.trade_size), + # time_in_force=TimeInForce.FOK, + ) + + # LONG suffix is recognized as a long position by Binance adapter. + position_id = PositionId(f"{self.instrument_id}-LONG") + self.submit_order(order, position_id) + + def sell(self) -> None: + """ + Users simple sell method (example). + """ + order: MarketOrder = self.order_factory.market( + instrument_id=self.instrument_id, + order_side=OrderSide.SELL, + quantity=self.instrument.make_qty(self.trade_size), + # time_in_force=TimeInForce.FOK, + ) + # SHORT suffix is recognized as a short position by Binance adapter. + position_id = PositionId(f"{self.instrument_id}-SHORT") + self.submit_order(order, position_id) + ``` + ## Order books Order books can be maintained at full or partial depths depending on the From 70dd43072aa2bc5bd6f0ac1aae524c0311a7f75d Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 18:15:20 +1000 Subject: [PATCH 65/72] Cleanup docs --- docs/integrations/binance.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/integrations/binance.md b/docs/integrations/binance.md index 6c5f522b6e52..a39bb5e76ef1 100644 --- a/docs/integrations/binance.md +++ b/docs/integrations/binance.md @@ -229,11 +229,13 @@ instrument_provider=InstrumentProviderConfig( ``` ### Futures Hedge mode -Binance Futures Hedge mode is a position mode where a trader opens positions in both long and short directions to mitigate risk and potentially profit from market volatility. Before starting a + +Binance Futures Hedge mode is a position mode where a trader opens positions in both long and short +directions to mitigate risk and potentially profit from market volatility. To use Binance Future Hedge mode, you need to follow the three items below: -1. Before starting the strategy, please ensure that hedge mode is configured on Binance. -2. Set the `use_reduce_only` option to `False` in BinanceExecClientConfig (this is `True` by default.) +- 1. Before starting the strategy, ensure that hedge mode is configured on Binance. +- 2. Set the `use_reduce_only` option to `False` in BinanceExecClientConfig (this is `True` by default.) ```python config = TradingNodeConfig( ..., # Omitted @@ -259,8 +261,7 @@ To use Binance Future Hedge mode, you need to follow the three items below: ) ``` - -3. When submitting an order, use a suffix (`LONG` or `SHORT` ) in the position_id to indicate the position direction. +- 3. When submitting an order, use a suffix (`LONG` or `SHORT` ) in the `position_id` to indicate the position direction. ```python class EMACrossHedgeMode(Strategy): ..., # Omitted From 224f15f75138101ebbc5c82638201a4cfcd7ca9e Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 21:39:43 +1000 Subject: [PATCH 66/72] Refine value object exceptions from pyo3 --- .../model/src/python/identifiers/instrument_id.rs | 6 ++++-- .../model/src/python/identifiers/trade_id.rs | 6 ++++-- nautilus_core/model/src/python/macros.rs | 5 +++-- nautilus_core/model/src/python/types/balance.rs | 7 ++++--- nautilus_core/model/src/python/types/currency.rs | 11 ++++++----- nautilus_core/model/src/python/types/money.rs | 5 +++-- nautilus_core/model/src/python/types/price.rs | 6 ++++-- nautilus_core/model/src/python/types/quantity.rs | 6 ++++-- tests/unit_tests/model/objects/test_money_pyo3.py | 6 +++--- tests/unit_tests/model/objects/test_price_pyo3.py | 8 ++++---- tests/unit_tests/model/objects/test_quantity_pyo3.py | 6 +++--- tests/unit_tests/model/test_currency_pyo3.py | 2 +- tests/unit_tests/model/test_identifiers_pyo3.py | 2 +- 13 files changed, 44 insertions(+), 32 deletions(-) diff --git a/nautilus_core/model/src/python/identifiers/instrument_id.rs b/nautilus_core/model/src/python/identifiers/instrument_id.rs index ef1d0db61070..746173361941 100644 --- a/nautilus_core/model/src/python/identifiers/instrument_id.rs +++ b/nautilus_core/model/src/python/identifiers/instrument_id.rs @@ -21,6 +21,7 @@ use std::{ use nautilus_core::python::to_pyvalue_err; use pyo3::{ + exceptions::PyValueError, prelude::*, pyclass::CompareOp, types::{PyString, PyTuple}, @@ -31,8 +32,9 @@ use crate::identifiers::{InstrumentId, Symbol, Venue}; #[pymethods] impl InstrumentId { #[new] - fn py_new(symbol: Symbol, venue: Venue) -> Self { - Self::new(symbol, venue) + fn py_new(symbol: Symbol, venue: Venue) -> PyResult { + std::panic::catch_unwind(|| Self::new(symbol, venue)) + .map_err(|e| PyErr::new::(format!("{e:?}"))) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/identifiers/trade_id.rs b/nautilus_core/model/src/python/identifiers/trade_id.rs index d5b7b617dbaa..46960e2674c0 100644 --- a/nautilus_core/model/src/python/identifiers/trade_id.rs +++ b/nautilus_core/model/src/python/identifiers/trade_id.rs @@ -22,6 +22,7 @@ use std::{ use nautilus_core::python::to_pyvalue_err; use pyo3::{ + exceptions::PyValueError, prelude::*, pyclass::CompareOp, types::{PyString, PyTuple}, @@ -32,8 +33,9 @@ use crate::identifiers::trade_id::TradeId; #[pymethods] impl TradeId { #[new] - fn py_new(value: &str) -> Self { - Self::new(value) + fn py_new(value: &str) -> PyResult { + std::panic::catch_unwind(|| Self::new(value)) + .map_err(|e| PyErr::new::(format!("{e:?}"))) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/macros.rs b/nautilus_core/model/src/python/macros.rs index cc1413f2557c..8632c6c85131 100644 --- a/nautilus_core/model/src/python/macros.rs +++ b/nautilus_core/model/src/python/macros.rs @@ -21,8 +21,9 @@ macro_rules! identifier_for_python { #[pymethods] impl $ty { #[new] - fn py_new(value: &str) -> Self { - <$ty>::new(value) + fn py_new(value: &str) -> PyResult { + std::panic::catch_unwind(|| <$ty>::new(value)) + .map_err(|e| PyErr::new::(format!("{e:?}"))) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/types/balance.rs b/nautilus_core/model/src/python/types/balance.rs index e38bbd47e516..443d33b44506 100644 --- a/nautilus_core/model/src/python/types/balance.rs +++ b/nautilus_core/model/src/python/types/balance.rs @@ -16,7 +16,7 @@ use std::str::FromStr; use nautilus_core::python::to_pyvalue_err; -use pyo3::{basic::CompareOp, prelude::*, types::PyDict}; +use pyo3::{basic::CompareOp, exceptions::PyValueError, prelude::*, types::PyDict}; use crate::{ identifiers::InstrumentId, @@ -30,8 +30,9 @@ use crate::{ #[pymethods] impl AccountBalance { #[new] - fn py_new(total: Money, locked: Money, free: Money) -> Self { - Self::new(total, locked, free) + fn py_new(total: Money, locked: Money, free: Money) -> PyResult { + std::panic::catch_unwind(|| Self::new(total, locked, free)) + .map_err(|e| PyErr::new::(format!("{e:?}"))) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/types/currency.rs b/nautilus_core/model/src/python/types/currency.rs index 711856f8dead..e3f43bddbe8b 100644 --- a/nautilus_core/model/src/python/types/currency.rs +++ b/nautilus_core/model/src/python/types/currency.rs @@ -15,9 +15,9 @@ use std::str::FromStr; -use nautilus_core::python::to_pyvalue_err; +use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err}; use pyo3::{ - exceptions::PyRuntimeError, + exceptions::{PyRuntimeError, PyValueError}, prelude::*, pyclass::CompareOp, types::{PyLong, PyString, PyTuple}, @@ -35,8 +35,9 @@ impl Currency { iso4217: u16, name: &str, currency_type: CurrencyType, - ) -> Self { - Self::new(code, precision, iso4217, name, currency_type) + ) -> PyResult { + std::panic::catch_unwind(|| Self::new(code, precision, iso4217, name, currency_type)) + .map_err(|e| PyErr::new::(format!("{e:?}"))) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { @@ -159,6 +160,6 @@ impl Currency { #[pyo3(name = "register")] #[pyo3(signature = (currency, overwrite = false))] fn py_register(currency: Self, overwrite: bool) -> PyResult<()> { - Self::register(currency, overwrite).map_err(|e| PyRuntimeError::new_err(e.to_string())) + Self::register(currency, overwrite).map_err(to_pyruntime_err) } } diff --git a/nautilus_core/model/src/python/types/money.rs b/nautilus_core/model/src/python/types/money.rs index 23128e999ac1..b4be2449359a 100644 --- a/nautilus_core/model/src/python/types/money.rs +++ b/nautilus_core/model/src/python/types/money.rs @@ -34,8 +34,9 @@ use crate::types::{currency::Currency, money::Money}; #[pymethods] impl Money { #[new] - fn py_new(value: f64, currency: Currency) -> Self { - Self::new(value, currency) + fn py_new(value: f64, currency: Currency) -> PyResult { + std::panic::catch_unwind(|| Self::new(value, currency)) + .map_err(|e| PyErr::new::(format!("{e:?}"))) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/types/price.rs b/nautilus_core/model/src/python/types/price.rs index a24412c099fa..c97600a91452 100644 --- a/nautilus_core/model/src/python/types/price.rs +++ b/nautilus_core/model/src/python/types/price.rs @@ -22,6 +22,7 @@ use std::{ use nautilus_core::python::{get_pytype_name, to_pytype_err, to_pyvalue_err}; use pyo3::{ + exceptions::PyValueError, prelude::*, pyclass::CompareOp, types::{PyFloat, PyLong, PyTuple}, @@ -33,8 +34,9 @@ use crate::types::{fixed::fixed_i64_to_f64, price::Price}; #[pymethods] impl Price { #[new] - fn py_new(value: f64, precision: u8) -> Self { - Self::new(value, precision) + fn py_new(value: f64, precision: u8) -> PyResult { + std::panic::catch_unwind(|| Self::new(value, precision)) + .map_err(|e| PyErr::new::(format!("{e:?}"))) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/types/quantity.rs b/nautilus_core/model/src/python/types/quantity.rs index b292b9aef2dd..3e347368de00 100644 --- a/nautilus_core/model/src/python/types/quantity.rs +++ b/nautilus_core/model/src/python/types/quantity.rs @@ -22,6 +22,7 @@ use std::{ use nautilus_core::python::{get_pytype_name, to_pytype_err, to_pyvalue_err}; use pyo3::{ + exceptions::PyValueError, prelude::*, pyclass::CompareOp, types::{PyFloat, PyLong, PyTuple}, @@ -33,8 +34,9 @@ use crate::types::quantity::Quantity; #[pymethods] impl Quantity { #[new] - fn py_new(value: f64, precision: u8) -> Self { - Self::new(value, precision) + fn py_new(value: f64, precision: u8) -> PyResult { + std::panic::catch_unwind(|| Self::new(value, precision)) + .map_err(|e| PyErr::new::(format!("{e:?}"))) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/tests/unit_tests/model/objects/test_money_pyo3.py b/tests/unit_tests/model/objects/test_money_pyo3.py index 6063526fbb12..69a0278bbe63 100644 --- a/tests/unit_tests/model/objects/test_money_pyo3.py +++ b/tests/unit_tests/model/objects/test_money_pyo3.py @@ -31,7 +31,7 @@ class TestMoney: def test_instantiate_with_nan_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Money(math.nan, currency=USD) def test_instantiate_with_none_value_raises_type_error(self) -> None: @@ -46,12 +46,12 @@ def test_instantiate_with_none_currency_raises_type_error(self) -> None: def test_instantiate_with_value_exceeding_positive_limit_raises_value_error(self) -> None: # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Money(9_223_372_036 + 1, currency=USD) def test_instantiate_with_value_exceeding_negative_limit_raises_value_error(self) -> None: # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Money(-9_223_372_036 - 1, currency=USD) @pytest.mark.parametrize( diff --git a/tests/unit_tests/model/objects/test_price_pyo3.py b/tests/unit_tests/model/objects/test_price_pyo3.py index b1ccf1afe15e..d2ca72095dda 100644 --- a/tests/unit_tests/model/objects/test_price_pyo3.py +++ b/tests/unit_tests/model/objects/test_price_pyo3.py @@ -25,7 +25,7 @@ class TestPrice: def test_instantiate_with_nan_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Price(math.nan, precision=0) def test_instantiate_with_none_value_raises_type_error(self): @@ -40,17 +40,17 @@ def test_instantiate_with_negative_precision_raises_overflow_error(self): def test_instantiate_with_precision_over_maximum_raises_overflow_error(self): # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Price(1.0, precision=10) def test_instantiate_with_value_exceeding_positive_limit_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Price(9_223_372_036 + 1, precision=0) def test_instantiate_with_value_exceeding_negative_limit_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Price(-9_223_372_036 - 1, precision=0) def test_instantiate_base_decimal_from_int(self): diff --git a/tests/unit_tests/model/objects/test_quantity_pyo3.py b/tests/unit_tests/model/objects/test_quantity_pyo3.py index a664055d3f85..4ea689e1d5ae 100644 --- a/tests/unit_tests/model/objects/test_quantity_pyo3.py +++ b/tests/unit_tests/model/objects/test_quantity_pyo3.py @@ -25,7 +25,7 @@ class TestQuantity: def test_instantiate_with_nan_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Quantity(math.nan, precision=0) def test_instantiate_with_none_value_raises_type_error(self): @@ -43,12 +43,12 @@ def test_instantiate_with_negative_precision_raises_overflow_error(self): def test_instantiate_with_precision_over_maximum_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Quantity(1.0, precision=10) def test_instantiate_with_value_exceeding_limit_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Quantity(18_446_744_073 + 1, precision=0) def test_instantiate_base_decimal_from_int(self): diff --git a/tests/unit_tests/model/test_currency_pyo3.py b/tests/unit_tests/model/test_currency_pyo3.py index 0f2e0695ae30..7ab128f18b28 100644 --- a/tests/unit_tests/model/test_currency_pyo3.py +++ b/tests/unit_tests/model/test_currency_pyo3.py @@ -41,7 +41,7 @@ def test_currency_with_negative_precision_raises_overflow_error(self): def test_currency_with_precision_over_maximum_raises_value_error(self): # Arrange, Act, Assert - with pytest.raises(BaseException): + with pytest.raises(ValueError): Currency( code="AUD", precision=10, diff --git a/tests/unit_tests/model/test_identifiers_pyo3.py b/tests/unit_tests/model/test_identifiers_pyo3.py index 0c551fa8816b..24dcfb66a5b3 100644 --- a/tests/unit_tests/model/test_identifiers_pyo3.py +++ b/tests/unit_tests/model/test_identifiers_pyo3.py @@ -200,7 +200,7 @@ def test_instrument_id_from_str() -> None: ) def test_instrument_id_from_str_when_invalid(input: str, expected_err: str) -> None: # Arrange, Act - with pytest.raises(BaseException) as exc_info: + with pytest.raises(ValueError) as exc_info: InstrumentId.from_str(input) # Assert From da91d591e59a9cbff15a984ef42f63880729f522 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Sun, 18 Aug 2024 22:21:32 +1000 Subject: [PATCH 67/72] Refine value object exceptions from pyo3 --- nautilus_core/model/src/identifiers/trade_id.rs | 4 ++-- .../src/python/identifiers/instrument_id.rs | 3 +-- .../model/src/python/identifiers/trade_id.rs | 11 ++++++----- nautilus_core/model/src/python/macros.rs | 5 +++-- nautilus_core/model/src/python/types/balance.rs | 3 +-- .../model/src/python/types/currency.rs | 16 ++++++++++++---- nautilus_core/model/src/python/types/money.rs | 17 ++++++++++++----- nautilus_core/model/src/python/types/price.rs | 16 ++++++++++++---- .../model/src/python/types/quantity.rs | 16 ++++++++++++---- nautilus_trader/core/includes/model.h | 7 ++++++- nautilus_trader/core/rust/model.pxd | 5 ++++- 11 files changed, 71 insertions(+), 32 deletions(-) diff --git a/nautilus_core/model/src/identifiers/trade_id.rs b/nautilus_core/model/src/identifiers/trade_id.rs index eba7e009832d..1e5372172de5 100644 --- a/nautilus_core/model/src/identifiers/trade_id.rs +++ b/nautilus_core/model/src/identifiers/trade_id.rs @@ -25,7 +25,7 @@ use nautilus_core::correctness::{check_in_range_inclusive_usize, check_valid_str use serde::{Deserialize, Deserializer, Serialize}; /// The maximum length of ASCII characters for a `TradeId` string value (including null terminator). -const TRADE_ID_LEN: usize = 37; +pub const TRADE_ID_LEN: usize = 37; /// Represents a valid trade match ID (assigned by a trading venue). /// @@ -38,7 +38,7 @@ const TRADE_ID_LEN: usize = 37; )] pub struct TradeId { /// The trade match ID value as a fixed-length C string byte array (includes null terminator). - pub(crate) value: [u8; 37], // cbindgen issue using the constant in the array + pub(crate) value: [u8; TRADE_ID_LEN], } impl TradeId { diff --git a/nautilus_core/model/src/python/identifiers/instrument_id.rs b/nautilus_core/model/src/python/identifiers/instrument_id.rs index 746173361941..537d3e41e5fc 100644 --- a/nautilus_core/model/src/python/identifiers/instrument_id.rs +++ b/nautilus_core/model/src/python/identifiers/instrument_id.rs @@ -33,8 +33,7 @@ use crate::identifiers::{InstrumentId, Symbol, Venue}; impl InstrumentId { #[new] fn py_new(symbol: Symbol, venue: Venue) -> PyResult { - std::panic::catch_unwind(|| Self::new(symbol, venue)) - .map_err(|e| PyErr::new::(format!("{e:?}"))) + Ok(Self::new(symbol, venue)) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/identifiers/trade_id.rs b/nautilus_core/model/src/python/identifiers/trade_id.rs index 46960e2674c0..978ca0b9c6c2 100644 --- a/nautilus_core/model/src/python/identifiers/trade_id.rs +++ b/nautilus_core/model/src/python/identifiers/trade_id.rs @@ -20,7 +20,7 @@ use std::{ str::FromStr, }; -use nautilus_core::python::to_pyvalue_err; +use nautilus_core::{correctness::check_in_range_inclusive_usize, python::to_pyvalue_err}; use pyo3::{ exceptions::PyValueError, prelude::*, @@ -28,14 +28,15 @@ use pyo3::{ types::{PyString, PyTuple}, }; -use crate::identifiers::trade_id::TradeId; +use crate::identifiers::trade_id::{TradeId, TRADE_ID_LEN}; #[pymethods] impl TradeId { #[new] fn py_new(value: &str) -> PyResult { - std::panic::catch_unwind(|| Self::new(value)) - .map_err(|e| PyErr::new::(format!("{e:?}"))) + check_in_range_inclusive_usize(value.len(), 1, TRADE_ID_LEN, stringify!(value)) + .map_err(to_pyvalue_err)?; + Ok(Self::new(value)) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { @@ -45,7 +46,7 @@ impl TradeId { // TODO: Extract this to single function let c_string = CString::new(value_str).expect("`CString` conversion failed"); let bytes = c_string.as_bytes_with_nul(); - let mut value = [0; 37]; + let mut value = [0; TRADE_ID_LEN]; value[..bytes.len()].copy_from_slice(bytes); self.value = value; diff --git a/nautilus_core/model/src/python/macros.rs b/nautilus_core/model/src/python/macros.rs index 8632c6c85131..fda5f1893fff 100644 --- a/nautilus_core/model/src/python/macros.rs +++ b/nautilus_core/model/src/python/macros.rs @@ -22,8 +22,9 @@ macro_rules! identifier_for_python { impl $ty { #[new] fn py_new(value: &str) -> PyResult { - std::panic::catch_unwind(|| <$ty>::new(value)) - .map_err(|e| PyErr::new::(format!("{e:?}"))) + nautilus_core::correctness::check_valid_string(value, stringify!(value)) + .map_err(to_pyvalue_err)?; + Ok(<$ty>::new(value)) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/types/balance.rs b/nautilus_core/model/src/python/types/balance.rs index 443d33b44506..5af13205d915 100644 --- a/nautilus_core/model/src/python/types/balance.rs +++ b/nautilus_core/model/src/python/types/balance.rs @@ -31,8 +31,7 @@ use crate::{ impl AccountBalance { #[new] fn py_new(total: Money, locked: Money, free: Money) -> PyResult { - std::panic::catch_unwind(|| Self::new(total, locked, free)) - .map_err(|e| PyErr::new::(format!("{e:?}"))) + Ok(Self::new(total, locked, free)) } fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { diff --git a/nautilus_core/model/src/python/types/currency.rs b/nautilus_core/model/src/python/types/currency.rs index e3f43bddbe8b..6a819b29d6f5 100644 --- a/nautilus_core/model/src/python/types/currency.rs +++ b/nautilus_core/model/src/python/types/currency.rs @@ -15,7 +15,10 @@ use std::str::FromStr; -use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err}; +use nautilus_core::{ + correctness::check_valid_string, + python::{to_pyruntime_err, to_pyvalue_err}, +}; use pyo3::{ exceptions::{PyRuntimeError, PyValueError}, prelude::*, @@ -24,7 +27,10 @@ use pyo3::{ }; use ustr::Ustr; -use crate::{enums::CurrencyType, types::currency::Currency}; +use crate::{ + enums::CurrencyType, + types::{currency::Currency, fixed::check_fixed_precision}, +}; #[pymethods] impl Currency { @@ -36,8 +42,10 @@ impl Currency { name: &str, currency_type: CurrencyType, ) -> PyResult { - std::panic::catch_unwind(|| Self::new(code, precision, iso4217, name, currency_type)) - .map_err(|e| PyErr::new::(format!("{e:?}"))) + check_valid_string(code, "code").map_err(to_pyvalue_err)?; + check_valid_string(name, "name").map_err(to_pyvalue_err)?; + check_fixed_precision(precision).map_err(to_pyvalue_err)?; + Ok(Self::new(code, precision, iso4217, name, currency_type)) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/types/money.rs b/nautilus_core/model/src/python/types/money.rs index b4be2449359a..7660805818e4 100644 --- a/nautilus_core/model/src/python/types/money.rs +++ b/nautilus_core/model/src/python/types/money.rs @@ -20,7 +20,10 @@ use std::{ str::FromStr, }; -use nautilus_core::python::{get_pytype_name, to_pytype_err, to_pyvalue_err}; +use nautilus_core::{ + correctness::check_in_range_inclusive_f64, + python::{get_pytype_name, to_pytype_err, to_pyvalue_err}, +}; use pyo3::{ exceptions::PyValueError, prelude::*, @@ -29,14 +32,18 @@ use pyo3::{ }; use rust_decimal::{Decimal, RoundingStrategy}; -use crate::types::{currency::Currency, money::Money}; +use crate::types::{ + currency::Currency, + money::{Money, MONEY_MAX, MONEY_MIN}, +}; #[pymethods] impl Money { #[new] - fn py_new(value: f64, currency: Currency) -> PyResult { - std::panic::catch_unwind(|| Self::new(value, currency)) - .map_err(|e| PyErr::new::(format!("{e:?}"))) + fn py_new(amount: f64, currency: Currency) -> PyResult { + check_in_range_inclusive_f64(amount, MONEY_MIN, MONEY_MAX, "amount") + .map_err(to_pyvalue_err)?; + Ok(Self::new(amount, currency)) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/types/price.rs b/nautilus_core/model/src/python/types/price.rs index c97600a91452..a8fe21435140 100644 --- a/nautilus_core/model/src/python/types/price.rs +++ b/nautilus_core/model/src/python/types/price.rs @@ -20,7 +20,10 @@ use std::{ str::FromStr, }; -use nautilus_core::python::{get_pytype_name, to_pytype_err, to_pyvalue_err}; +use nautilus_core::{ + correctness::check_in_range_inclusive_f64, + python::{get_pytype_name, to_pytype_err, to_pyvalue_err}, +}; use pyo3::{ exceptions::PyValueError, prelude::*, @@ -29,14 +32,19 @@ use pyo3::{ }; use rust_decimal::{Decimal, RoundingStrategy}; -use crate::types::{fixed::fixed_i64_to_f64, price::Price}; +use crate::types::{ + fixed::{check_fixed_precision, fixed_i64_to_f64}, + price::{Price, PRICE_MAX, PRICE_MIN}, +}; #[pymethods] impl Price { #[new] fn py_new(value: f64, precision: u8) -> PyResult { - std::panic::catch_unwind(|| Self::new(value, precision)) - .map_err(|e| PyErr::new::(format!("{e:?}"))) + check_in_range_inclusive_f64(value, PRICE_MIN, PRICE_MAX, "value") + .map_err(to_pyvalue_err)?; + check_fixed_precision(precision).map_err(to_pyvalue_err)?; + Ok(Self::new(value, precision)) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_core/model/src/python/types/quantity.rs b/nautilus_core/model/src/python/types/quantity.rs index 3e347368de00..a43772d231a8 100644 --- a/nautilus_core/model/src/python/types/quantity.rs +++ b/nautilus_core/model/src/python/types/quantity.rs @@ -20,7 +20,10 @@ use std::{ str::FromStr, }; -use nautilus_core::python::{get_pytype_name, to_pytype_err, to_pyvalue_err}; +use nautilus_core::{ + correctness::check_in_range_inclusive_f64, + python::{get_pytype_name, to_pytype_err, to_pyvalue_err}, +}; use pyo3::{ exceptions::PyValueError, prelude::*, @@ -29,14 +32,19 @@ use pyo3::{ }; use rust_decimal::{Decimal, RoundingStrategy}; -use crate::types::quantity::Quantity; +use crate::types::{ + fixed::check_fixed_precision, + quantity::{Quantity, QUANTITY_MAX, QUANTITY_MIN}, +}; #[pymethods] impl Quantity { #[new] fn py_new(value: f64, precision: u8) -> PyResult { - std::panic::catch_unwind(|| Self::new(value, precision)) - .map_err(|e| PyErr::new::(format!("{e:?}"))) + check_in_range_inclusive_f64(value, QUANTITY_MIN, QUANTITY_MAX, "value") + .map_err(to_pyvalue_err)?; + check_fixed_precision(precision).map_err(to_pyvalue_err)?; + Ok(Self::new(value, precision)) } fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> { diff --git a/nautilus_trader/core/includes/model.h b/nautilus_trader/core/includes/model.h index 41abc425e10d..b0f9850c19d5 100644 --- a/nautilus_trader/core/includes/model.h +++ b/nautilus_trader/core/includes/model.h @@ -7,6 +7,11 @@ #define DEPTH10_LEN 10 +/** + * The maximum length of ASCII characters for a `TradeId` string value (including null terminator). + */ +#define TRADE_ID_LEN 37 + #define FIXED_PRECISION 9 #define FIXED_SCALAR 1000000000.0 @@ -1008,7 +1013,7 @@ typedef struct TradeId_t { /** * The trade match ID value as a fixed-length C string byte array (includes null terminator). */ - uint8_t value[37]; + uint8_t value[TRADE_ID_LEN]; } TradeId_t; /** diff --git a/nautilus_trader/core/rust/model.pxd b/nautilus_trader/core/rust/model.pxd index c41b62c06597..677a89e23d46 100644 --- a/nautilus_trader/core/rust/model.pxd +++ b/nautilus_trader/core/rust/model.pxd @@ -7,6 +7,9 @@ cdef extern from "../includes/model.h": const uintptr_t DEPTH10_LEN # = 10 + # The maximum length of ASCII characters for a `TradeId` string value (including null terminator). + const uintptr_t TRADE_ID_LEN # = 37 + const uint8_t FIXED_PRECISION # = 9 const double FIXED_SCALAR # = 1000000000.0 @@ -551,7 +554,7 @@ cdef extern from "../includes/model.h": # Can correspond to the `TradeID <1003> field` of the FIX protocol. cdef struct TradeId_t: # The trade match ID value as a fixed-length C string byte array (includes null terminator). - uint8_t value[37]; + uint8_t value[TRADE_ID_LEN]; # Represents a single trade tick in a market. cdef struct TradeTick_t: From 33605c07aeb813bc1bc603b6012fef197aa46baa Mon Sep 17 00:00:00 2001 From: Ishan Bhanuka Date: Sun, 18 Aug 2024 17:21:11 -0400 Subject: [PATCH 68/72] Improve indicators by reducing Result return type (#1858) --- nautilus_core/common/src/clock.rs | 24 +++---- nautilus_core/common/src/ffi/clock.rs | 16 ++--- nautilus_core/common/src/python/clock.rs | 24 ++----- nautilus_core/common/src/timer.rs | 36 +++++------ nautilus_core/data/src/aggregation.rs | 2 +- nautilus_core/indicators/src/average/ama.rs | 11 ++-- nautilus_core/indicators/src/average/dema.rs | 10 +-- nautilus_core/indicators/src/average/ema.rs | 8 +-- nautilus_core/indicators/src/average/hma.rs | 12 ++-- nautilus_core/indicators/src/average/lr.rs | 6 +- nautilus_core/indicators/src/average/mod.rs | 16 ++--- nautilus_core/indicators/src/average/rma.rs | 8 +-- nautilus_core/indicators/src/average/sma.rs | 8 +-- nautilus_core/indicators/src/average/vidya.rs | 8 +-- nautilus_core/indicators/src/average/vwap.rs | 8 +-- nautilus_core/indicators/src/average/wma.rs | 26 ++++---- .../indicators/src/book/imbalance.rs | 22 +++---- nautilus_core/indicators/src/momentum/amat.rs | 7 ++- .../indicators/src/momentum/aroon.rs | 20 +++--- nautilus_core/indicators/src/momentum/bb.rs | 6 +- nautilus_core/indicators/src/momentum/bias.rs | 8 +-- nautilus_core/indicators/src/momentum/cci.rs | 10 +-- nautilus_core/indicators/src/momentum/cmo.rs | 6 +- nautilus_core/indicators/src/momentum/dm.rs | 6 +- nautilus_core/indicators/src/momentum/kvo.rs | 6 +- nautilus_core/indicators/src/momentum/macd.rs | 6 +- nautilus_core/indicators/src/momentum/obv.rs | 6 +- .../indicators/src/momentum/pressure.rs | 12 ++-- nautilus_core/indicators/src/momentum/psl.rs | 6 +- nautilus_core/indicators/src/momentum/roc.rs | 6 +- nautilus_core/indicators/src/momentum/rsi.rs | 6 +- .../indicators/src/momentum/stochastics.rs | 6 +- .../indicators/src/momentum/swings.rs | 6 +- nautilus_core/indicators/src/momentum/vhf.rs | 6 +- .../indicators/src/python/average/ama.rs | 3 +- .../indicators/src/python/average/dema.rs | 4 +- .../indicators/src/python/average/ema.rs | 4 +- .../indicators/src/python/average/hma.rs | 4 +- .../indicators/src/python/average/lr.rs | 4 +- .../indicators/src/python/average/rma.rs | 4 +- .../indicators/src/python/average/sma.rs | 4 +- .../indicators/src/python/average/vidya.rs | 4 +- .../indicators/src/python/average/vwap.rs | 4 +- .../indicators/src/python/average/wma.rs | 8 +-- .../indicators/src/python/book/imbalance.rs | 4 +- .../indicators/src/python/momentum/amat.rs | 4 +- .../indicators/src/python/momentum/aroon.rs | 4 +- .../indicators/src/python/momentum/bb.rs | 4 +- .../indicators/src/python/momentum/bias.rs | 4 +- .../indicators/src/python/momentum/cci.rs | 8 +-- .../indicators/src/python/momentum/cmo.rs | 4 +- .../indicators/src/python/momentum/dm.rs | 4 +- .../indicators/src/python/momentum/kvo.rs | 4 +- .../indicators/src/python/momentum/macd.rs | 4 +- .../indicators/src/python/momentum/obv.rs | 4 +- .../src/python/momentum/pressure.rs | 4 +- .../indicators/src/python/momentum/psl.rs | 4 +- .../indicators/src/python/momentum/roc.rs | 4 +- .../indicators/src/python/momentum/rsi.rs | 4 +- .../src/python/momentum/stochastics.rs | 4 +- .../indicators/src/python/momentum/swings.rs | 4 +- .../indicators/src/python/momentum/vhf.rs | 4 +- .../src/python/ratio/efficiency_ratio.rs | 4 +- .../src/python/ratio/spread_analyzer.rs | 4 +- .../indicators/src/python/volatility/atr.rs | 4 +- .../indicators/src/python/volatility/dc.rs | 4 +- .../indicators/src/python/volatility/fuzzy.rs | 7 +-- .../indicators/src/python/volatility/kc.rs | 3 +- .../indicators/src/python/volatility/kp.rs | 3 +- .../indicators/src/python/volatility/rvi.rs | 8 +-- .../indicators/src/python/volatility/vr.rs | 3 +- .../indicators/src/ratio/efficiency_ratio.rs | 6 +- .../indicators/src/ratio/spread_analyzer.rs | 6 +- nautilus_core/indicators/src/stubs.rs | 63 +++++++++---------- nautilus_core/indicators/src/testing.rs | 2 + .../indicators/src/volatility/atr.rs | 45 ++++++------- nautilus_core/indicators/src/volatility/dc.rs | 6 +- .../indicators/src/volatility/fuzzy.rs | 21 +++---- nautilus_core/indicators/src/volatility/kc.rs | 8 +-- nautilus_core/indicators/src/volatility/kp.rs | 9 ++- .../indicators/src/volatility/rvi.rs | 10 +-- nautilus_core/indicators/src/volatility/vr.rs | 10 +-- 82 files changed, 314 insertions(+), 405 deletions(-) diff --git a/nautilus_core/common/src/clock.rs b/nautilus_core/common/src/clock.rs index 99121cd93f34..be6e059147ee 100644 --- a/nautilus_core/common/src/clock.rs +++ b/nautilus_core/common/src/clock.rs @@ -69,7 +69,7 @@ pub trait Clock { name: &str, alert_time_ns: UnixNanos, callback: Option, - ) -> anyhow::Result<()>; + ); /// Set a `Timer` to start alerting at every interval /// between start and stop time. Optional callback gets @@ -81,7 +81,7 @@ pub trait Clock { start_time_ns: UnixNanos, stop_time_ns: Option, callback: Option, - ) -> anyhow::Result<()>; + ); fn next_time_ns(&self, name: &str) -> UnixNanos; fn cancel_timer(&mut self, name: &str); @@ -227,7 +227,7 @@ impl Clock for TestClock { name: &str, alert_time_ns: UnixNanos, callback: Option, - ) -> anyhow::Result<()> { + ) { check_valid_string(name, stringify!(name)).expect(FAILED); check_predicate_true( callback.is_some() | self.default_callback.is_some(), @@ -247,9 +247,8 @@ impl Clock for TestClock { (alert_time_ns - time_ns).into(), time_ns, Some(alert_time_ns), - )?; + ); self.timers.insert(name_ustr, timer); - Ok(()) } fn set_timer_ns( @@ -259,7 +258,7 @@ impl Clock for TestClock { start_time_ns: UnixNanos, stop_time_ns: Option, callback: Option, - ) -> anyhow::Result<()> { + ) { check_valid_string(name, "name").expect(FAILED); check_positive_u64(interval_ns, stringify!(interval_ns)).expect(FAILED); check_predicate_true( @@ -274,9 +273,8 @@ impl Clock for TestClock { None => None, }; - let timer = TestTimer::new(name, interval_ns, start_time_ns, stop_time_ns)?; + let timer = TestTimer::new(name, interval_ns, start_time_ns, stop_time_ns); self.timers.insert(name_ustr, timer); - Ok(()) } fn next_time_ns(&self, name: &str) -> UnixNanos { @@ -385,7 +383,7 @@ impl Clock for LiveClock { name: &str, mut alert_time_ns: UnixNanos, callback: Option, - ) -> anyhow::Result<()> { + ) { check_valid_string(name, stringify!(name)).expect(FAILED); assert!( callback.is_some() | self.default_callback.is_some(), @@ -400,11 +398,10 @@ impl Clock for LiveClock { let ts_now = self.get_time_ns(); alert_time_ns = std::cmp::max(alert_time_ns, ts_now); let interval_ns = (alert_time_ns - ts_now).into(); - let mut timer = LiveTimer::new(name, interval_ns, ts_now, Some(alert_time_ns), callback)?; + let mut timer = LiveTimer::new(name, interval_ns, ts_now, Some(alert_time_ns), callback); timer.start(); self.timers.insert(Ustr::from(name), timer); - Ok(()) } fn set_timer_ns( @@ -414,7 +411,7 @@ impl Clock for LiveClock { start_time_ns: UnixNanos, stop_time_ns: Option, callback: Option, - ) -> anyhow::Result<()> { + ) { check_valid_string(name, stringify!(name)).expect(FAILED); check_positive_u64(interval_ns, stringify!(interval_ns)).expect(FAILED); check_predicate_true( @@ -428,10 +425,9 @@ impl Clock for LiveClock { None => self.default_callback.clone().unwrap(), }; - let mut timer = LiveTimer::new(name, interval_ns, start_time_ns, stop_time_ns, callback)?; + let mut timer = LiveTimer::new(name, interval_ns, start_time_ns, stop_time_ns, callback); timer.start(); self.timers.insert(Ustr::from(name), timer); - Ok(()) } fn next_time_ns(&self, name: &str) -> UnixNanos { diff --git a/nautilus_core/common/src/ffi/clock.rs b/nautilus_core/common/src/ffi/clock.rs index 5766c80cbb51..735871a17abc 100644 --- a/nautilus_core/common/src/ffi/clock.rs +++ b/nautilus_core/common/src/ffi/clock.rs @@ -152,9 +152,7 @@ pub unsafe extern "C" fn test_clock_set_time_alert( } }; - clock - .set_time_alert_ns(name, alert_time_ns, handler) - .unwrap(); + clock.set_time_alert_ns(name, alert_time_ns, handler); } /// # Safety @@ -185,9 +183,7 @@ pub unsafe extern "C" fn test_clock_set_timer( } }; - clock - .set_timer_ns(name, interval_ns, start_time_ns, stop_time_ns, handler) - .unwrap(); + clock.set_timer_ns(name, interval_ns, start_time_ns, stop_time_ns, handler); } /// # Safety @@ -362,9 +358,7 @@ pub unsafe extern "C" fn live_clock_set_time_alert( } }; - clock - .set_time_alert_ns(name, alert_time_ns, handler) - .unwrap(); + clock.set_time_alert_ns(name, alert_time_ns, handler); } /// # Safety @@ -401,9 +395,7 @@ pub unsafe extern "C" fn live_clock_set_timer( } }; - clock - .set_timer_ns(name, interval_ns, start_time_ns, stop_time_ns, handler) - .unwrap(); + clock.set_timer_ns(name, interval_ns, start_time_ns, stop_time_ns, handler); } /// # Safety diff --git a/nautilus_core/common/src/python/clock.rs b/nautilus_core/common/src/python/clock.rs index ba7e650ad0d9..149072d092a4 100644 --- a/nautilus_core/common/src/python/clock.rs +++ b/nautilus_core/common/src/python/clock.rs @@ -44,9 +44,7 @@ mod tests { test_clock.register_default_handler(handler); let timer_name = "TEST_TIME1"; - test_clock - .set_timer_ns(timer_name, 10, 0.into(), None, None) - .unwrap(); + test_clock.set_timer_ns(timer_name, 10, 0.into(), None, None); assert_eq!(test_clock.timer_names(), [timer_name]); assert_eq!(test_clock.timer_count(), 1); @@ -64,9 +62,7 @@ mod tests { test_clock.register_default_handler(handler); let timer_name = "TEST_TIME1"; - test_clock - .set_timer_ns(timer_name, 10, 0.into(), None, None) - .unwrap(); + test_clock.set_timer_ns(timer_name, 10, 0.into(), None, None); test_clock.cancel_timer(timer_name); assert!(test_clock.timer_names().is_empty()); @@ -85,9 +81,7 @@ mod tests { test_clock.register_default_handler(handler); let timer_name = "TEST_TIME1"; - test_clock - .set_timer_ns(timer_name, 10, 0.into(), None, None) - .unwrap(); + test_clock.set_timer_ns(timer_name, 10, 0.into(), None, None); test_clock.cancel_timers(); assert!(test_clock.timer_names().is_empty()); @@ -106,9 +100,7 @@ mod tests { test_clock.register_default_handler(handler); let timer_name = "TEST_TIME1"; - test_clock - .set_timer_ns(timer_name, 1, 1.into(), Some(UnixNanos::from(3)), None) - .unwrap(); + test_clock.set_timer_ns(timer_name, 1, 1.into(), Some(UnixNanos::from(3)), None); test_clock.advance_time(2.into(), true); assert_eq!(test_clock.timer_names(), [timer_name]); @@ -126,9 +118,7 @@ mod tests { let handler = EventHandler::new(py_append); test_clock.register_default_handler(handler); - test_clock - .set_timer_ns("TEST_TIME1", 2, 0.into(), Some(UnixNanos::from(3)), None) - .unwrap(); + test_clock.set_timer_ns("TEST_TIME1", 2, 0.into(), Some(UnixNanos::from(3)), None); test_clock.advance_time(3.into(), true); assert_eq!(test_clock.timer_names().len(), 1); @@ -147,9 +137,7 @@ mod tests { let handler = EventHandler::new(py_append); test_clock.register_default_handler(handler); - test_clock - .set_timer_ns("TEST_TIME1", 2, 0.into(), Some(UnixNanos::from(3)), None) - .unwrap(); + test_clock.set_timer_ns("TEST_TIME1", 2, 0.into(), Some(UnixNanos::from(3)), None); test_clock.advance_time(3.into(), false); assert_eq!(test_clock.timer_names().len(), 1); diff --git a/nautilus_core/common/src/timer.rs b/nautilus_core/common/src/timer.rs index 0bb47b5affac..e698f30b3a8a 100644 --- a/nautilus_core/common/src/timer.rs +++ b/nautilus_core/common/src/timer.rs @@ -140,19 +140,19 @@ impl TestTimer { interval_ns: u64, start_time_ns: UnixNanos, stop_time_ns: Option, - ) -> anyhow::Result { + ) -> Self { check_valid_string(name, stringify!(name)).expect(FAILED); // SAFETY: Guaranteed to be non-zero let interval_ns = NonZeroU64::new(std::cmp::max(interval_ns, 1)).unwrap(); - Ok(Self { + Self { name: Ustr::from(name), interval_ns, start_time_ns, stop_time_ns, next_time_ns: start_time_ns + interval_ns.get(), is_expired: false, - }) + } } /// Returns the next time in UNIX nanoseconds when the timer will fire. @@ -244,13 +244,13 @@ impl LiveTimer { start_time_ns: UnixNanos, stop_time_ns: Option, callback: EventHandler, - ) -> anyhow::Result { + ) -> Self { check_valid_string(name, stringify!(name)).expect(FAILED); // SAFETY: Guaranteed to be non-zero let interval_ns = NonZeroU64::new(std::cmp::max(interval_ns, 1)).unwrap(); log::debug!("Creating timer '{}'", name); - Ok(Self { + Self { name: Ustr::from(name), interval_ns, start_time_ns, @@ -259,7 +259,7 @@ impl LiveTimer { is_expired: Arc::new(AtomicBool::new(false)), callback, canceler: None, - }) + } } /// Returns the next time in UNIX nanoseconds when the timer will fire. @@ -406,7 +406,7 @@ mod tests { #[rstest] fn test_test_timer_pop_event() { - let mut timer = TestTimer::new("test_timer", 1, UnixNanos::from(1), None).unwrap(); + let mut timer = TestTimer::new("test_timer", 1, UnixNanos::from(1), None); assert!(timer.next().is_some()); assert!(timer.next().is_some()); @@ -416,7 +416,7 @@ mod tests { #[rstest] fn test_test_timer_advance_within_next_time_ns() { - let mut timer = TestTimer::new("test_timer", 5, UnixNanos::default(), None).unwrap(); + let mut timer = TestTimer::new("test_timer", 5, UnixNanos::default(), None); let _: Vec = timer.advance(UnixNanos::from(1)).collect(); let _: Vec = timer.advance(UnixNanos::from(2)).collect(); let _: Vec = timer.advance(UnixNanos::from(3)).collect(); @@ -427,7 +427,7 @@ mod tests { #[rstest] fn test_test_timer_advance_up_to_next_time_ns() { - let mut timer = TestTimer::new("test_timer", 1, UnixNanos::default(), None).unwrap(); + let mut timer = TestTimer::new("test_timer", 1, UnixNanos::default(), None); assert_eq!(timer.advance(UnixNanos::from(1)).count(), 1); assert!(!timer.is_expired); } @@ -439,8 +439,7 @@ mod tests { 1, UnixNanos::default(), Some(UnixNanos::from(2)), - ) - .unwrap(); + ); assert_eq!(timer.advance(UnixNanos::from(2)).count(), 2); assert!(timer.is_expired); } @@ -452,8 +451,7 @@ mod tests { 1, UnixNanos::default(), Some(UnixNanos::from(5)), - ) - .unwrap(); + ); assert_eq!(timer.advance(UnixNanos::from(5)).count(), 5); assert!(timer.is_expired); } @@ -465,8 +463,7 @@ mod tests { 1, UnixNanos::default(), Some(UnixNanos::from(5)), - ) - .unwrap(); + ); assert_eq!(timer.advance(UnixNanos::from(10)).count(), 5); assert!(timer.is_expired); } @@ -484,8 +481,7 @@ mod tests { let clock = get_atomic_clock_realtime(); let start_time = clock.get_time_ns(); let interval_ns = 100 * NANOSECONDS_IN_MILLISECOND; - let mut timer = - LiveTimer::new("TEST_TIMER", interval_ns, start_time, None, handler).unwrap(); + let mut timer = LiveTimer::new("TEST_TIMER", interval_ns, start_time, None, handler); let next_time_ns = timer.next_time_ns(); timer.start(); @@ -517,8 +513,7 @@ mod tests { start_time, Some(stop_time), handler, - ) - .unwrap(); + ); let next_time_ns = timer.next_time_ns(); timer.start(); @@ -549,8 +544,7 @@ mod tests { start_time, Some(stop_time), handler, - ) - .unwrap(); + ); timer.start(); wait_until(|| timer.is_expired(), Duration::from_secs(2)); diff --git a/nautilus_core/data/src/aggregation.rs b/nautilus_core/data/src/aggregation.rs index 9c5aefbb9e7e..8a22e6b35843 100644 --- a/nautilus_core/data/src/aggregation.rs +++ b/nautilus_core/data/src/aggregation.rs @@ -509,7 +509,7 @@ where start_time_ns, None, None, // TODO: Implement Rust callback handlers properly (see above commented code) - )?; + ); log::debug!("Started timer {}", self.timer_name); Ok(()) diff --git a/nautilus_core/indicators/src/average/ama.rs b/nautilus_core/indicators/src/average/ama.rs index 53b4d6a72965..892668f817f2 100644 --- a/nautilus_core/indicators/src/average/ama.rs +++ b/nautilus_core/indicators/src/average/ama.rs @@ -110,10 +110,8 @@ impl AdaptiveMovingAverage { period_fast: usize, period_slow: usize, price_type: Option, - ) -> anyhow::Result { - // Inputs don't require validation, however we return a `Result` - // to standardize with other indicators which do need validation. - Ok(Self { + ) -> Self { + Self { period_efficiency_ratio, period_fast, period_slow, @@ -125,8 +123,8 @@ impl AdaptiveMovingAverage { prior_value: None, has_inputs: false, initialized: false, - efficiency_ratio: EfficiencyRatio::new(period_efficiency_ratio, price_type)?, - }) + efficiency_ratio: EfficiencyRatio::new(period_efficiency_ratio, price_type), + } } #[must_use] @@ -171,6 +169,7 @@ impl MovingAverage for AdaptiveMovingAverage { .powi(2); // Calculate the AMA + // TODO: Remove unwraps self.value = smoothing_constant .mul_add(value - self.prior_value.unwrap(), self.prior_value.unwrap()); if self.efficiency_ratio.initialized() { diff --git a/nautilus_core/indicators/src/average/dema.rs b/nautilus_core/indicators/src/average/dema.rs index 05728e1bd80b..43c6b3511a7a 100644 --- a/nautilus_core/indicators/src/average/dema.rs +++ b/nautilus_core/indicators/src/average/dema.rs @@ -88,17 +88,17 @@ impl Indicator for DoubleExponentialMovingAverage { impl DoubleExponentialMovingAverage { /// Creates a new [`DoubleExponentialMovingAverage`] instance. - pub fn new(period: usize, price_type: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, price_type: Option) -> Self { + Self { period, price_type: price_type.unwrap_or(PriceType::Last), value: 0.0, count: 0, has_inputs: false, initialized: false, - ema1: ExponentialMovingAverage::new(period, price_type)?, - ema2: ExponentialMovingAverage::new(period, price_type)?, - }) + ema1: ExponentialMovingAverage::new(period, price_type), + ema2: ExponentialMovingAverage::new(period, price_type), + } } } diff --git a/nautilus_core/indicators/src/average/ema.rs b/nautilus_core/indicators/src/average/ema.rs index e43f57371e48..e2c919a6a007 100644 --- a/nautilus_core/indicators/src/average/ema.rs +++ b/nautilus_core/indicators/src/average/ema.rs @@ -79,10 +79,8 @@ impl Indicator for ExponentialMovingAverage { impl ExponentialMovingAverage { /// Creates a new [`ExponentialMovingAverage`] instance. - pub fn new(period: usize, price_type: Option) -> anyhow::Result { - // Inputs don't require validation, however we return a `Result` - // to standardize with other indicators which do need validation. - Ok(Self { + pub fn new(period: usize, price_type: Option) -> Self { + Self { period, price_type: price_type.unwrap_or(PriceType::Last), alpha: 2.0 / (period as f64 + 1.0), @@ -90,7 +88,7 @@ impl ExponentialMovingAverage { count: 0, has_inputs: false, initialized: false, - }) + } } } diff --git a/nautilus_core/indicators/src/average/hma.rs b/nautilus_core/indicators/src/average/hma.rs index b848722f0a10..a72a5e06482c 100644 --- a/nautilus_core/indicators/src/average/hma.rs +++ b/nautilus_core/indicators/src/average/hma.rs @@ -97,7 +97,7 @@ fn _get_weights(size: usize) -> Vec { impl HullMovingAverage { /// Creates a new [`HullMovingAverage`] instance. - pub fn new(period: usize, price_type: Option) -> anyhow::Result { + pub fn new(period: usize, price_type: Option) -> Self { let period_halved = period / 2; let period_sqrt = (period as f64).sqrt() as usize; @@ -105,11 +105,11 @@ impl HullMovingAverage { let w2 = _get_weights(period); let w3 = _get_weights(period_sqrt); - let ma1 = WeightedMovingAverage::new(period_halved, w1, price_type)?; - let ma2 = WeightedMovingAverage::new(period, w2, price_type)?; - let ma3 = WeightedMovingAverage::new(period_sqrt, w3, price_type)?; + let ma1 = WeightedMovingAverage::new(period_halved, w1, price_type); + let ma2 = WeightedMovingAverage::new(period, w2, price_type); + let ma3 = WeightedMovingAverage::new(period_sqrt, w3, price_type); - Ok(Self { + Self { period, price_type: price_type.unwrap_or(PriceType::Last), value: 0.0, @@ -119,7 +119,7 @@ impl HullMovingAverage { ma1, ma2, ma3, - }) + } } } diff --git a/nautilus_core/indicators/src/average/lr.rs b/nautilus_core/indicators/src/average/lr.rs index 3c5afd6a4d65..e9c2aa7996f4 100644 --- a/nautilus_core/indicators/src/average/lr.rs +++ b/nautilus_core/indicators/src/average/lr.rs @@ -76,8 +76,8 @@ impl Indicator for LinearRegression { impl LinearRegression { /// Creates a new [`LinearRegression`] instance. - pub fn new(period: usize) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize) -> Self { + Self { period, slope: 0.0, intercept: 0.0, @@ -88,7 +88,7 @@ impl LinearRegression { inputs: Vec::with_capacity(period), has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, close: f64) { diff --git a/nautilus_core/indicators/src/average/mod.rs b/nautilus_core/indicators/src/average/mod.rs index 4d2156d066c9..bf31b49f04f9 100644 --- a/nautilus_core/indicators/src/average/mod.rs +++ b/nautilus_core/indicators/src/average/mod.rs @@ -78,21 +78,15 @@ impl MovingAverageFactory { let price_type = Some(PriceType::Last); match moving_average_type { - MovingAverageType::Simple => { - Box::new(SimpleMovingAverage::new(period, price_type).unwrap()) - } + MovingAverageType::Simple => Box::new(SimpleMovingAverage::new(period, price_type)), MovingAverageType::Exponential => { - Box::new(ExponentialMovingAverage::new(period, price_type).unwrap()) + Box::new(ExponentialMovingAverage::new(period, price_type)) } MovingAverageType::DoubleExponential => { - Box::new(DoubleExponentialMovingAverage::new(period, price_type).unwrap()) - } - MovingAverageType::Wilder => { - Box::new(WilderMovingAverage::new(period, price_type).unwrap()) - } - MovingAverageType::Hull => { - Box::new(HullMovingAverage::new(period, price_type).unwrap()) + Box::new(DoubleExponentialMovingAverage::new(period, price_type)) } + MovingAverageType::Wilder => Box::new(WilderMovingAverage::new(period, price_type)), + MovingAverageType::Hull => Box::new(HullMovingAverage::new(period, price_type)), } } } diff --git a/nautilus_core/indicators/src/average/rma.rs b/nautilus_core/indicators/src/average/rma.rs index ab735e50e937..154f41f1d679 100644 --- a/nautilus_core/indicators/src/average/rma.rs +++ b/nautilus_core/indicators/src/average/rma.rs @@ -79,13 +79,11 @@ impl Indicator for WilderMovingAverage { impl WilderMovingAverage { /// Creates a new [`WilderMovingAverage`] instance. - pub fn new(period: usize, price_type: Option) -> anyhow::Result { - // Inputs don't require validation, however we return a `Result` - // to standardize with other indicators which do need validation. + pub fn new(period: usize, price_type: Option) -> Self { // The Wilder Moving Average is The Wilder's Moving Average is simply // an Exponential Moving Average (EMA) with a modified alpha. // alpha = 1 / period - Ok(Self { + Self { period, price_type: price_type.unwrap_or(PriceType::Last), alpha: 1.0 / (period as f64), @@ -93,7 +91,7 @@ impl WilderMovingAverage { count: 0, has_inputs: false, initialized: false, - }) + } } } diff --git a/nautilus_core/indicators/src/average/sma.rs b/nautilus_core/indicators/src/average/sma.rs index 9e465883a9e1..8fd6311ac3e0 100644 --- a/nautilus_core/indicators/src/average/sma.rs +++ b/nautilus_core/indicators/src/average/sma.rs @@ -78,17 +78,15 @@ impl Indicator for SimpleMovingAverage { impl SimpleMovingAverage { /// Creates a new [`SimpleMovingAverage`] instance. - pub fn new(period: usize, price_type: Option) -> anyhow::Result { - // Inputs don't require validation, however we return a `Result` - // to standardize with other indicators which do need validation. - Ok(Self { + pub fn new(period: usize, price_type: Option) -> Self { + Self { period, price_type: price_type.unwrap_or(PriceType::Last), value: 0.0, count: 0, inputs: Vec::with_capacity(period), initialized: false, - }) + } } } diff --git a/nautilus_core/indicators/src/average/vidya.rs b/nautilus_core/indicators/src/average/vidya.rs index ee94e76a61df..4e1d424e0e01 100644 --- a/nautilus_core/indicators/src/average/vidya.rs +++ b/nautilus_core/indicators/src/average/vidya.rs @@ -91,8 +91,8 @@ impl VariableIndexDynamicAverage { period: usize, price_type: Option, cmo_ma_type: Option, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { period, price_type: price_type.unwrap_or(PriceType::Last), value: 0.0, @@ -100,9 +100,9 @@ impl VariableIndexDynamicAverage { has_inputs: false, initialized: false, alpha: 2.0 / (period as f64 + 1.0), - cmo: ChandeMomentumOscillator::new(period, cmo_ma_type)?, + cmo: ChandeMomentumOscillator::new(period, cmo_ma_type), cmo_pct: 0.0, - }) + } } } diff --git a/nautilus_core/indicators/src/average/vwap.rs b/nautilus_core/indicators/src/average/vwap.rs index 8dfab07725e0..65ae5e64b856 100644 --- a/nautilus_core/indicators/src/average/vwap.rs +++ b/nautilus_core/indicators/src/average/vwap.rs @@ -20,7 +20,7 @@ use nautilus_model::data::bar::Bar; use crate::indicator::Indicator; #[repr(C)] -#[derive(Debug)] +#[derive(Debug, Default)] #[cfg_attr( feature = "python", pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators") @@ -72,15 +72,15 @@ impl Indicator for VolumeWeightedAveragePrice { impl VolumeWeightedAveragePrice { /// Creates a new [`VolumeWeightedAveragePrice`] instance. - pub const fn new() -> anyhow::Result { - Ok(Self { + pub const fn new() -> Self { + Self { value: 0.0, has_inputs: false, initialized: false, price_volume: 0.0, volume_total: 0.0, day: 0.0, - }) + } } pub fn update_raw(&mut self, price: f64, volume: f64, timestamp: f64) { diff --git a/nautilus_core/indicators/src/average/wma.rs b/nautilus_core/indicators/src/average/wma.rs index c11a0bb23688..6c99b4e0375b 100644 --- a/nautilus_core/indicators/src/average/wma.rs +++ b/nautilus_core/indicators/src/average/wma.rs @@ -53,15 +53,13 @@ impl Display for WeightedMovingAverage { impl WeightedMovingAverage { /// Creates a new [`WeightedMovingAverage`] instance. - pub fn new( - period: usize, - weights: Vec, - price_type: Option, - ) -> anyhow::Result { - if weights.len() != period { - return Err(anyhow::anyhow!("Weights length must be equal to period")); - } - Ok(Self { + pub fn new(period: usize, weights: Vec, price_type: Option) -> Self { + assert_eq!( + weights.len(), + period, + "Weights length must be equal to period" + ); + Self { period, weights, price_type: price_type.unwrap_or(PriceType::Last), @@ -69,7 +67,7 @@ impl WeightedMovingAverage { inputs: Vec::with_capacity(period), initialized: false, has_inputs: false, - }) + } } fn weighted_average(&self) -> f64 { @@ -169,9 +167,9 @@ mod tests { } #[rstest] + #[should_panic] fn test_different_weights_len_and_period_error() { - let wma = WeightedMovingAverage::new(10, vec![0.5, 0.5, 0.5], None); - assert!(wma.is_err()); + WeightedMovingAverage::new(10, vec![0.5, 0.5, 0.5], None); } #[rstest] @@ -182,7 +180,7 @@ mod tests { #[rstest] fn test_value_with_two_inputs_equal_weights() { - let mut wma = WeightedMovingAverage::new(2, vec![0.5, 0.5], None).unwrap(); + let mut wma = WeightedMovingAverage::new(2, vec![0.5, 0.5], None); wma.update_raw(1.0); wma.update_raw(2.0); assert_eq!(wma.value, 1.5); @@ -190,7 +188,7 @@ mod tests { #[rstest] fn test_value_with_four_inputs_equal_weights() { - let mut wma = WeightedMovingAverage::new(4, vec![0.25, 0.25, 0.25, 0.25], None).unwrap(); + let mut wma = WeightedMovingAverage::new(4, vec![0.25, 0.25, 0.25, 0.25], None); wma.update_raw(1.0); wma.update_raw(2.0); wma.update_raw(3.0); diff --git a/nautilus_core/indicators/src/book/imbalance.rs b/nautilus_core/indicators/src/book/imbalance.rs index 65279f4762cc..c02b1ce3cb5f 100644 --- a/nautilus_core/indicators/src/book/imbalance.rs +++ b/nautilus_core/indicators/src/book/imbalance.rs @@ -20,7 +20,7 @@ use nautilus_model::{orderbook::book::OrderBook, types::quantity::Quantity}; use crate::indicator::Indicator; #[repr(C)] -#[derive(Debug)] +#[derive(Debug, Default)] #[cfg_attr( feature = "python", pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators") @@ -65,15 +65,13 @@ impl Indicator for BookImbalanceRatio { impl BookImbalanceRatio { /// Creates a new [`BookImbalanceRatio`] instance. - pub const fn new() -> anyhow::Result { - // Inputs don't require validation, however we return a `Result` - // to standardize with other indicators which do need validation. - Ok(Self { + pub const fn new() -> Self { + Self { value: 0.0, count: 0, has_inputs: false, initialized: false, - }) + } } pub fn update(&mut self, best_bid: Option, best_ask: Option) { @@ -108,7 +106,7 @@ mod tests { #[rstest] fn test_initialized() { - let imbalance = BookImbalanceRatio::new().unwrap(); + let imbalance = BookImbalanceRatio::new(); let display_str = format!("{imbalance}"); assert_eq!(display_str, "BookImbalanceRatio()"); assert_eq!(imbalance.value, 0.0); @@ -119,7 +117,7 @@ mod tests { #[rstest] fn test_one_value_input_balanced() { - let mut imbalance = BookImbalanceRatio::new().unwrap(); + let mut imbalance = BookImbalanceRatio::new(); let book = stub_order_book_mbp_appl_xnas(); imbalance.handle_book(&book); @@ -131,7 +129,7 @@ mod tests { #[rstest] fn test_reset() { - let mut imbalance = BookImbalanceRatio::new().unwrap(); + let mut imbalance = BookImbalanceRatio::new(); let book = stub_order_book_mbp_appl_xnas(); imbalance.handle_book(&book); imbalance.reset(); @@ -144,7 +142,7 @@ mod tests { #[rstest] fn test_one_value_input_with_bid_imbalance() { - let mut imbalance = BookImbalanceRatio::new().unwrap(); + let mut imbalance = BookImbalanceRatio::new(); let book = stub_order_book_mbp( InstrumentId::from("AAPL.XNAS"), 101.0, @@ -167,7 +165,7 @@ mod tests { #[rstest] fn test_one_value_input_with_ask_imbalance() { - let mut imbalance = BookImbalanceRatio::new().unwrap(); + let mut imbalance = BookImbalanceRatio::new(); let book = stub_order_book_mbp( InstrumentId::from("AAPL.XNAS"), 101.0, @@ -190,7 +188,7 @@ mod tests { #[rstest] fn test_one_value_input_with_bid_imbalance_multiple_inputs() { - let mut imbalance = BookImbalanceRatio::new().unwrap(); + let mut imbalance = BookImbalanceRatio::new(); let book = stub_order_book_mbp( InstrumentId::from("AAPL.XNAS"), 101.0, diff --git a/nautilus_core/indicators/src/momentum/amat.rs b/nautilus_core/indicators/src/momentum/amat.rs index 6eb22bbed563..f943ebf75f01 100644 --- a/nautilus_core/indicators/src/momentum/amat.rs +++ b/nautilus_core/indicators/src/momentum/amat.rs @@ -96,8 +96,8 @@ impl ArcherMovingAveragesTrends { slow_period: usize, signal_period: usize, ma_type: Option, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { fast_period, slow_period, signal_period, @@ -116,7 +116,7 @@ impl ArcherMovingAveragesTrends { slow_ma_price: VecDeque::with_capacity(signal_period + 1), has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, close: f64) { @@ -127,6 +127,7 @@ impl ArcherMovingAveragesTrends { self.fast_ma_price.push_back(self.fast_ma.value()); self.slow_ma_price.push_back(self.slow_ma.value()); + // TODO: Remove unwraps self.long_run = self.fast_ma_price.back().unwrap() - self.fast_ma_price.front().unwrap() > 0.0 diff --git a/nautilus_core/indicators/src/momentum/aroon.rs b/nautilus_core/indicators/src/momentum/aroon.rs index 1538737c7b01..2ef5e7e103ee 100644 --- a/nautilus_core/indicators/src/momentum/aroon.rs +++ b/nautilus_core/indicators/src/momentum/aroon.rs @@ -92,8 +92,8 @@ impl Indicator for AroonOscillator { impl AroonOscillator { /// Creates a new [`AroonOscillator`] instance. - pub fn new(period: usize) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize) -> Self { + Self { period, high_inputs: VecDeque::with_capacity(period), low_inputs: VecDeque::with_capacity(period), @@ -103,7 +103,7 @@ impl AroonOscillator { count: 0, has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, high: f64, low: f64) { @@ -180,25 +180,25 @@ mod tests { #[rstest] fn test_name_returns_expected_string() { - let aroon = AroonOscillator::new(10).unwrap(); + let aroon = AroonOscillator::new(10); assert_eq!(aroon.name(), "AroonOscillator"); } #[rstest] fn test_period() { - let aroon = AroonOscillator::new(10).unwrap(); + let aroon = AroonOscillator::new(10); assert_eq!(aroon.period, 10); } #[rstest] fn test_initialized_without_inputs_returns_false() { - let aroon = AroonOscillator::new(10).unwrap(); + let aroon = AroonOscillator::new(10); assert!(!aroon.initialized()); } #[rstest] fn test_initialized_with_required_inputs_returns_true() { - let mut aroon = AroonOscillator::new(10).unwrap(); + let mut aroon = AroonOscillator::new(10); for _ in 0..20 { aroon.update_raw(110.08, 109.61); } @@ -207,7 +207,7 @@ mod tests { #[rstest] fn test_value_with_one_input() { - let mut aroon = AroonOscillator::new(1).unwrap(); + let mut aroon = AroonOscillator::new(1); aroon.update_raw(110.08, 109.61); assert_eq!(aroon.aroon_up, 100.0); assert_eq!(aroon.aroon_down, 100.0); @@ -216,7 +216,7 @@ mod tests { #[rstest] fn test_value_with_twenty_inputs() { - let mut aroon = AroonOscillator::new(20).unwrap(); + let mut aroon = AroonOscillator::new(20); let inputs = [ (110.08, 109.61), (110.15, 109.91), @@ -249,7 +249,7 @@ mod tests { #[rstest] fn test_reset_successfully_returns_indicator_to_fresh_state() { - let mut aroon = AroonOscillator::new(10).unwrap(); + let mut aroon = AroonOscillator::new(10); for _ in 0..1000 { aroon.update_raw(110.08, 109.61); } diff --git a/nautilus_core/indicators/src/momentum/bb.rs b/nautilus_core/indicators/src/momentum/bb.rs index 8275765491c0..4335660cbdfc 100644 --- a/nautilus_core/indicators/src/momentum/bb.rs +++ b/nautilus_core/indicators/src/momentum/bb.rs @@ -99,8 +99,8 @@ impl Indicator for BollingerBands { impl BollingerBands { /// Creates a new [`BollingerBands`] instance. - pub fn new(period: usize, k: f64, ma_type: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, k: f64, ma_type: Option) -> Self { + Self { period, k, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), @@ -111,7 +111,7 @@ impl BollingerBands { lower: 0.0, ma: MovingAverageFactory::create(ma_type.unwrap_or(MovingAverageType::Simple), period), prices: VecDeque::with_capacity(period), - }) + } } pub fn update_raw(&mut self, high: f64, low: f64, close: f64) { diff --git a/nautilus_core/indicators/src/momentum/bias.rs b/nautilus_core/indicators/src/momentum/bias.rs index 55f78453c344..b3891980ba6f 100644 --- a/nautilus_core/indicators/src/momentum/bias.rs +++ b/nautilus_core/indicators/src/momentum/bias.rs @@ -73,8 +73,8 @@ impl Indicator for Bias { impl Bias { /// Creates a new [`Bias`] instance. - pub fn new(period: usize, ma_type: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, ma_type: Option) -> Self { + Self { period, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), value: 0.0, @@ -83,7 +83,7 @@ impl Bias { ma: MovingAverageFactory::create(ma_type.unwrap_or(MovingAverageType::Simple), period), has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, close: f64) { @@ -114,7 +114,7 @@ mod tests { #[fixture] fn bias() -> Bias { - Bias::new(10, None).unwrap() + Bias::new(10, None) } #[rstest] diff --git a/nautilus_core/indicators/src/momentum/cci.rs b/nautilus_core/indicators/src/momentum/cci.rs index 55348ccb6715..2e0399b187a0 100644 --- a/nautilus_core/indicators/src/momentum/cci.rs +++ b/nautilus_core/indicators/src/momentum/cci.rs @@ -78,12 +78,8 @@ impl Indicator for CommodityChannelIndex { impl CommodityChannelIndex { /// Creates a new [`CommodityChannelIndex`] instance. - pub fn new( - period: usize, - scalar: f64, - ma_type: Option, - ) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, scalar: f64, ma_type: Option) -> Self { + Self { period, scalar, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), @@ -93,7 +89,7 @@ impl CommodityChannelIndex { has_inputs: false, initialized: false, mad: 0.0, - }) + } } pub fn update_raw(&mut self, high: f64, low: f64, close: f64) { diff --git a/nautilus_core/indicators/src/momentum/cmo.rs b/nautilus_core/indicators/src/momentum/cmo.rs index 9ac48ed42fc4..b59796d1aa9c 100644 --- a/nautilus_core/indicators/src/momentum/cmo.rs +++ b/nautilus_core/indicators/src/momentum/cmo.rs @@ -82,8 +82,8 @@ impl Indicator for ChandeMomentumOscillator { impl ChandeMomentumOscillator { /// Creates a new [`ChandeMomentumOscillator`] instance. - pub fn new(period: usize, ma_type: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, ma_type: Option) -> Self { + Self { period, ma_type: ma_type.unwrap_or(MovingAverageType::Wilder), average_gain: MovingAverageFactory::create(MovingAverageType::Wilder, period), @@ -93,7 +93,7 @@ impl ChandeMomentumOscillator { count: 0, initialized: false, has_inputs: false, - }) + } } pub fn update_raw(&mut self, close: f64) { diff --git a/nautilus_core/indicators/src/momentum/dm.rs b/nautilus_core/indicators/src/momentum/dm.rs index a56a0fee3d74..b71776eb5ca3 100644 --- a/nautilus_core/indicators/src/momentum/dm.rs +++ b/nautilus_core/indicators/src/momentum/dm.rs @@ -78,8 +78,8 @@ impl Indicator for DirectionalMovement { impl DirectionalMovement { /// Creates a new [`DirectionalMovement`] instance. - pub fn new(period: usize, ma_type: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, ma_type: Option) -> Self { + Self { period, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), pos: 0.0, @@ -96,7 +96,7 @@ impl DirectionalMovement { ), has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, high: f64, low: f64) { diff --git a/nautilus_core/indicators/src/momentum/kvo.rs b/nautilus_core/indicators/src/momentum/kvo.rs index 3733a2d149ce..a4dad03a4625 100644 --- a/nautilus_core/indicators/src/momentum/kvo.rs +++ b/nautilus_core/indicators/src/momentum/kvo.rs @@ -98,8 +98,8 @@ impl KlingerVolumeOscillator { slow_period: usize, signal_period: usize, ma_type: Option, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { fast_period, slow_period, signal_period, @@ -121,7 +121,7 @@ impl KlingerVolumeOscillator { hlc3: 0.0, previous_hlc3: 0.0, initialized: false, - }) + } } pub fn update_raw(&mut self, high: f64, low: f64, close: f64, volume: f64) { diff --git a/nautilus_core/indicators/src/momentum/macd.rs b/nautilus_core/indicators/src/momentum/macd.rs index 1d10037fef6c..0d9f77fb8399 100644 --- a/nautilus_core/indicators/src/momentum/macd.rs +++ b/nautilus_core/indicators/src/momentum/macd.rs @@ -99,8 +99,8 @@ impl MovingAverageConvergenceDivergence { slow_period: usize, ma_type: Option, price_type: Option, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { fast_period, slow_period, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), @@ -117,7 +117,7 @@ impl MovingAverageConvergenceDivergence { ma_type.unwrap_or(MovingAverageType::Simple), slow_period, ), - }) + } } } diff --git a/nautilus_core/indicators/src/momentum/obv.rs b/nautilus_core/indicators/src/momentum/obv.rs index f5cb01cd20be..4e4c1acc21bf 100644 --- a/nautilus_core/indicators/src/momentum/obv.rs +++ b/nautilus_core/indicators/src/momentum/obv.rs @@ -73,14 +73,14 @@ impl Indicator for OnBalanceVolume { impl OnBalanceVolume { /// Creates a new [`OnBalanceVolume`] instance. - pub fn new(period: usize) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize) -> Self { + Self { period, value: 0.0, obv: VecDeque::with_capacity(period), has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, open: f64, close: f64, volume: f64) { diff --git a/nautilus_core/indicators/src/momentum/pressure.rs b/nautilus_core/indicators/src/momentum/pressure.rs index f735f4766716..c947ef18b0df 100644 --- a/nautilus_core/indicators/src/momentum/pressure.rs +++ b/nautilus_core/indicators/src/momentum/pressure.rs @@ -81,12 +81,8 @@ impl Indicator for Pressure { impl Pressure { /// Creates a new [`Pressure`] instance. - pub fn new( - period: usize, - ma_type: Option, - atr_floor: Option, - ) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, ma_type: Option, atr_floor: Option) -> Self { + Self { period, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), atr_floor: atr_floor.unwrap_or(0.0), @@ -97,14 +93,14 @@ impl Pressure { Some(MovingAverageType::Exponential), Some(false), atr_floor, - )?, + ), average_volume: MovingAverageFactory::create( ma_type.unwrap_or(MovingAverageType::Simple), period, ), has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, high: f64, low: f64, close: f64, volume: f64) { diff --git a/nautilus_core/indicators/src/momentum/psl.rs b/nautilus_core/indicators/src/momentum/psl.rs index 79ce7a17a0a7..4ef7bb7ac5c5 100644 --- a/nautilus_core/indicators/src/momentum/psl.rs +++ b/nautilus_core/indicators/src/momentum/psl.rs @@ -74,8 +74,8 @@ impl Indicator for PsychologicalLine { impl PsychologicalLine { /// Creates a new [`PsychologicalLine`] instance. - pub fn new(period: usize, ma_type: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, ma_type: Option) -> Self { + Self { period, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), value: 0.0, @@ -84,7 +84,7 @@ impl PsychologicalLine { has_inputs: false, initialized: false, diff: 0.0, - }) + } } pub fn update_raw(&mut self, close: f64) { diff --git a/nautilus_core/indicators/src/momentum/roc.rs b/nautilus_core/indicators/src/momentum/roc.rs index 094acf739274..06d7baf8862b 100644 --- a/nautilus_core/indicators/src/momentum/roc.rs +++ b/nautilus_core/indicators/src/momentum/roc.rs @@ -70,15 +70,15 @@ impl Indicator for RateOfChange { impl RateOfChange { /// Creates a new [`RateOfChange`] instance. - pub fn new(period: usize, use_log: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, use_log: Option) -> Self { + Self { period, use_log: use_log.unwrap_or(false), value: 0.0, prices: VecDeque::with_capacity(period), has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, price: f64) { diff --git a/nautilus_core/indicators/src/momentum/rsi.rs b/nautilus_core/indicators/src/momentum/rsi.rs index 3be10825ebcf..556a1c4bb22d 100644 --- a/nautilus_core/indicators/src/momentum/rsi.rs +++ b/nautilus_core/indicators/src/momentum/rsi.rs @@ -87,8 +87,8 @@ impl Indicator for RelativeStrengthIndex { impl RelativeStrengthIndex { /// Creates a new [`RelativeStrengthIndex`] instance. - pub fn new(period: usize, ma_type: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, ma_type: Option) -> Self { + Self { period, ma_type: ma_type.unwrap_or(MovingAverageType::Exponential), value: 0.0, @@ -100,7 +100,7 @@ impl RelativeStrengthIndex { average_loss: MovingAverageFactory::create(MovingAverageType::Exponential, period), rsi_max: 1.0, initialized: false, - }) + } } pub fn update_raw(&mut self, value: f64) { diff --git a/nautilus_core/indicators/src/momentum/stochastics.rs b/nautilus_core/indicators/src/momentum/stochastics.rs index 04deb577f519..41e63a4f1f22 100644 --- a/nautilus_core/indicators/src/momentum/stochastics.rs +++ b/nautilus_core/indicators/src/momentum/stochastics.rs @@ -78,8 +78,8 @@ impl Indicator for Stochastics { impl Stochastics { /// Creates a new [`Stochastics`] instance. - pub fn new(period_k: usize, period_d: usize) -> anyhow::Result { - Ok(Self { + pub fn new(period_k: usize, period_d: usize) -> Self { + Self { period_k, period_d, has_inputs: false, @@ -90,7 +90,7 @@ impl Stochastics { lows: VecDeque::with_capacity(period_k), h_sub_l: VecDeque::with_capacity(period_d), c_sub_1: VecDeque::with_capacity(period_d), - }) + } } pub fn update_raw(&mut self, high: f64, low: f64, close: f64) { diff --git a/nautilus_core/indicators/src/momentum/swings.rs b/nautilus_core/indicators/src/momentum/swings.rs index 1a6fcb542c9f..46492b3b561d 100644 --- a/nautilus_core/indicators/src/momentum/swings.rs +++ b/nautilus_core/indicators/src/momentum/swings.rs @@ -89,8 +89,8 @@ impl Indicator for Swings { impl Swings { /// Creates a new [`Swings`] instance. - pub fn new(period: usize) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize) -> Self { + Self { period, high_inputs: VecDeque::with_capacity(period + 1), low_inputs: VecDeque::with_capacity(period + 1), @@ -106,7 +106,7 @@ impl Swings { duration: 0, since_high: 0, since_low: 0, - }) + } } pub fn update_raw(&mut self, high: f64, low: f64, timestamp: f64) { diff --git a/nautilus_core/indicators/src/momentum/vhf.rs b/nautilus_core/indicators/src/momentum/vhf.rs index 9da5b3df7259..7925abb9002d 100644 --- a/nautilus_core/indicators/src/momentum/vhf.rs +++ b/nautilus_core/indicators/src/momentum/vhf.rs @@ -77,8 +77,8 @@ impl Indicator for VerticalHorizontalFilter { impl VerticalHorizontalFilter { /// Creates a new [`VerticalHorizontalFilter`] instance. - pub fn new(period: usize, ma_type: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, ma_type: Option) -> Self { + Self { period, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), value: 0.0, @@ -87,7 +87,7 @@ impl VerticalHorizontalFilter { has_inputs: false, initialized: false, prices: VecDeque::with_capacity(period), - }) + } } pub fn update_raw(&mut self, close: f64) { diff --git a/nautilus_core/indicators/src/python/average/ama.rs b/nautilus_core/indicators/src/python/average/ama.rs index 7710e692adc2..9cecac7061cb 100644 --- a/nautilus_core/indicators/src/python/average/ama.rs +++ b/nautilus_core/indicators/src/python/average/ama.rs @@ -33,14 +33,13 @@ impl AdaptiveMovingAverage { period_fast: usize, period_slow: usize, price_type: Option, - ) -> PyResult { + ) -> Self { Self::new( period_efficiency_ratio, period_fast, period_slow, price_type, ) - .map_err(to_pyvalue_err) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/average/dema.rs b/nautilus_core/indicators/src/python/average/dema.rs index b3b26c7c2d8e..d7a099b37803 100644 --- a/nautilus_core/indicators/src/python/average/dema.rs +++ b/nautilus_core/indicators/src/python/average/dema.rs @@ -28,8 +28,8 @@ use crate::{ #[pymethods] impl DoubleExponentialMovingAverage { #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) + fn py_new(period: usize, price_type: Option) -> Self { + Self::new(period, price_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/average/ema.rs b/nautilus_core/indicators/src/python/average/ema.rs index 71cb1e67e414..8c2916e5d4d5 100644 --- a/nautilus_core/indicators/src/python/average/ema.rs +++ b/nautilus_core/indicators/src/python/average/ema.rs @@ -28,8 +28,8 @@ use crate::{ #[pymethods] impl ExponentialMovingAverage { #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) + fn py_new(period: usize, price_type: Option) -> Self { + Self::new(period, price_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/average/hma.rs b/nautilus_core/indicators/src/python/average/hma.rs index 3b4ad4967a6a..414675bfd1f6 100644 --- a/nautilus_core/indicators/src/python/average/hma.rs +++ b/nautilus_core/indicators/src/python/average/hma.rs @@ -28,8 +28,8 @@ use crate::{ #[pymethods] impl HullMovingAverage { #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) + fn py_new(period: usize, price_type: Option) -> Self { + Self::new(period, price_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/average/lr.rs b/nautilus_core/indicators/src/python/average/lr.rs index 70660d41a023..c2c8922336fa 100644 --- a/nautilus_core/indicators/src/python/average/lr.rs +++ b/nautilus_core/indicators/src/python/average/lr.rs @@ -25,8 +25,8 @@ use crate::{ #[pymethods] impl LinearRegression { #[new] - pub fn py_new(period: usize) -> PyResult { - Self::new(period).map_err(to_pyvalue_err) + pub fn py_new(period: usize) -> Self { + Self::new(period) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/average/rma.rs b/nautilus_core/indicators/src/python/average/rma.rs index f1bef3f6de2d..3c3f4f66462b 100644 --- a/nautilus_core/indicators/src/python/average/rma.rs +++ b/nautilus_core/indicators/src/python/average/rma.rs @@ -28,8 +28,8 @@ use crate::{ #[pymethods] impl WilderMovingAverage { #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) + fn py_new(period: usize, price_type: Option) -> Self { + Self::new(period, price_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/average/sma.rs b/nautilus_core/indicators/src/python/average/sma.rs index b9f6df39edb2..b81681cd3794 100644 --- a/nautilus_core/indicators/src/python/average/sma.rs +++ b/nautilus_core/indicators/src/python/average/sma.rs @@ -28,8 +28,8 @@ use crate::{ #[pymethods] impl SimpleMovingAverage { #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) + fn py_new(period: usize, price_type: Option) -> Self { + Self::new(period, price_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/average/vidya.rs b/nautilus_core/indicators/src/python/average/vidya.rs index a66b859926f3..c5269b698f34 100644 --- a/nautilus_core/indicators/src/python/average/vidya.rs +++ b/nautilus_core/indicators/src/python/average/vidya.rs @@ -32,8 +32,8 @@ impl VariableIndexDynamicAverage { period: usize, price_type: Option, cmo_ma_type: Option, - ) -> PyResult { - Self::new(period, price_type, cmo_ma_type).map_err(to_pyvalue_err) + ) -> Self { + Self::new(period, price_type, cmo_ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/average/vwap.rs b/nautilus_core/indicators/src/python/average/vwap.rs index 41df2d4faeeb..54a3ccb355b6 100644 --- a/nautilus_core/indicators/src/python/average/vwap.rs +++ b/nautilus_core/indicators/src/python/average/vwap.rs @@ -28,8 +28,8 @@ use crate::{ #[pymethods] impl VolumeWeightedAveragePrice { #[new] - pub fn py_new() -> PyResult { - Self::new().map_err(to_pyvalue_err) + pub fn py_new() -> Self { + Self::new() } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/average/wma.rs b/nautilus_core/indicators/src/python/average/wma.rs index ca500ae67f41..d14f8870ba88 100644 --- a/nautilus_core/indicators/src/python/average/wma.rs +++ b/nautilus_core/indicators/src/python/average/wma.rs @@ -28,12 +28,8 @@ use crate::{ #[pymethods] impl WeightedMovingAverage { #[new] - pub fn py_new( - period: usize, - weights: Vec, - price_type: Option, - ) -> PyResult { - Self::new(period, weights, price_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, weights: Vec, price_type: Option) -> Self { + Self::new(period, weights, price_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/book/imbalance.rs b/nautilus_core/indicators/src/python/book/imbalance.rs index 735ea129bade..e10c5521c9f5 100644 --- a/nautilus_core/indicators/src/python/book/imbalance.rs +++ b/nautilus_core/indicators/src/python/book/imbalance.rs @@ -22,8 +22,8 @@ use crate::{book::imbalance::BookImbalanceRatio, indicator::Indicator}; #[pymethods] impl BookImbalanceRatio { #[new] - fn py_new() -> PyResult { - Self::new().map_err(to_pyvalue_err) + fn py_new() -> Self { + Self::new() } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/amat.rs b/nautilus_core/indicators/src/python/momentum/amat.rs index e19da4c367ff..99947331b7d6 100644 --- a/nautilus_core/indicators/src/python/momentum/amat.rs +++ b/nautilus_core/indicators/src/python/momentum/amat.rs @@ -29,8 +29,8 @@ impl ArcherMovingAveragesTrends { slow_period: usize, signal_period: usize, ma_type: Option, - ) -> PyResult { - Self::new(fast_period, slow_period, signal_period, ma_type).map_err(to_pyvalue_err) + ) -> Self { + Self::new(fast_period, slow_period, signal_period, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/aroon.rs b/nautilus_core/indicators/src/python/momentum/aroon.rs index 5ef64bfe2193..e22171b75fb9 100644 --- a/nautilus_core/indicators/src/python/momentum/aroon.rs +++ b/nautilus_core/indicators/src/python/momentum/aroon.rs @@ -22,8 +22,8 @@ use crate::{indicator::Indicator, momentum::aroon::AroonOscillator}; #[pymethods] impl AroonOscillator { #[new] - pub fn py_new(period: usize) -> PyResult { - Self::new(period).map_err(to_pyvalue_err) + pub fn py_new(period: usize) -> Self { + Self::new(period) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/bb.rs b/nautilus_core/indicators/src/python/momentum/bb.rs index 76aec71c21fe..4a14a8531468 100644 --- a/nautilus_core/indicators/src/python/momentum/bb.rs +++ b/nautilus_core/indicators/src/python/momentum/bb.rs @@ -22,8 +22,8 @@ use crate::{average::MovingAverageType, indicator::Indicator, momentum::bb::Boll #[pymethods] impl BollingerBands { #[new] - pub fn py_new(period: usize, k: f64, ma_type: Option) -> PyResult { - Self::new(period, k, ma_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, k: f64, ma_type: Option) -> Self { + Self::new(period, k, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/bias.rs b/nautilus_core/indicators/src/python/momentum/bias.rs index 792ed88522e7..ebc252ecdb10 100644 --- a/nautilus_core/indicators/src/python/momentum/bias.rs +++ b/nautilus_core/indicators/src/python/momentum/bias.rs @@ -22,8 +22,8 @@ use crate::{average::MovingAverageType, indicator::Indicator, momentum::bias::Bi #[pymethods] impl Bias { #[new] - pub fn py_new(period: usize, ma_type: Option) -> PyResult { - Self::new(period, ma_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, ma_type: Option) -> Self { + Self::new(period, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/cci.rs b/nautilus_core/indicators/src/python/momentum/cci.rs index c8e1179f2f0b..667a53ed0a06 100644 --- a/nautilus_core/indicators/src/python/momentum/cci.rs +++ b/nautilus_core/indicators/src/python/momentum/cci.rs @@ -24,12 +24,8 @@ use crate::{ #[pymethods] impl CommodityChannelIndex { #[new] - pub fn py_new( - period: usize, - scalar: f64, - ma_type: Option, - ) -> PyResult { - Self::new(period, scalar, ma_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, scalar: f64, ma_type: Option) -> Self { + Self::new(period, scalar, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/cmo.rs b/nautilus_core/indicators/src/python/momentum/cmo.rs index a5f663878d00..18965e530811 100644 --- a/nautilus_core/indicators/src/python/momentum/cmo.rs +++ b/nautilus_core/indicators/src/python/momentum/cmo.rs @@ -24,8 +24,8 @@ use crate::{ #[pymethods] impl ChandeMomentumOscillator { #[new] - pub fn py_new(period: usize, ma_type: Option) -> PyResult { - Self::new(period, ma_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, ma_type: Option) -> Self { + Self::new(period, ma_type) } #[getter] diff --git a/nautilus_core/indicators/src/python/momentum/dm.rs b/nautilus_core/indicators/src/python/momentum/dm.rs index df68241f0277..d84d8640ac4a 100644 --- a/nautilus_core/indicators/src/python/momentum/dm.rs +++ b/nautilus_core/indicators/src/python/momentum/dm.rs @@ -22,8 +22,8 @@ use crate::{average::MovingAverageType, indicator::Indicator, momentum::dm::Dire #[pymethods] impl DirectionalMovement { #[new] - pub fn py_new(period: usize, ma_type: Option) -> PyResult { - Self::new(period, ma_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, ma_type: Option) -> Self { + Self::new(period, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/kvo.rs b/nautilus_core/indicators/src/python/momentum/kvo.rs index 8654611f3dd3..817f03945e78 100644 --- a/nautilus_core/indicators/src/python/momentum/kvo.rs +++ b/nautilus_core/indicators/src/python/momentum/kvo.rs @@ -29,8 +29,8 @@ impl KlingerVolumeOscillator { slow_period: usize, signal_period: usize, ma_type: Option, - ) -> PyResult { - Self::new(fast_period, slow_period, signal_period, ma_type).map_err(to_pyvalue_err) + ) -> Self { + Self::new(fast_period, slow_period, signal_period, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/macd.rs b/nautilus_core/indicators/src/python/momentum/macd.rs index 980dc5eac33f..6af976d311db 100644 --- a/nautilus_core/indicators/src/python/momentum/macd.rs +++ b/nautilus_core/indicators/src/python/momentum/macd.rs @@ -34,8 +34,8 @@ impl MovingAverageConvergenceDivergence { slow_period: usize, ma_type: Option, price_type: Option, - ) -> PyResult { - Self::new(fast_period, slow_period, ma_type, price_type).map_err(to_pyvalue_err) + ) -> Self { + Self::new(fast_period, slow_period, ma_type, price_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/obv.rs b/nautilus_core/indicators/src/python/momentum/obv.rs index bfb5e5742f5c..e92ac8d92972 100644 --- a/nautilus_core/indicators/src/python/momentum/obv.rs +++ b/nautilus_core/indicators/src/python/momentum/obv.rs @@ -22,8 +22,8 @@ use crate::{average::MovingAverageType, indicator::Indicator, momentum::obv::OnB #[pymethods] impl OnBalanceVolume { #[new] - pub fn py_new(period: usize) -> PyResult { - Self::new(period).map_err(to_pyvalue_err) + pub fn py_new(period: usize) -> Self { + Self::new(period) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/pressure.rs b/nautilus_core/indicators/src/python/momentum/pressure.rs index 12a2cd585e0b..caa300f37a68 100644 --- a/nautilus_core/indicators/src/python/momentum/pressure.rs +++ b/nautilus_core/indicators/src/python/momentum/pressure.rs @@ -26,8 +26,8 @@ impl Pressure { period: usize, ma_type: Option, atr_floor: Option, - ) -> PyResult { - Self::new(period, ma_type, atr_floor).map_err(to_pyvalue_err) + ) -> Self { + Self::new(period, ma_type, atr_floor) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/psl.rs b/nautilus_core/indicators/src/python/momentum/psl.rs index 6efc21111050..3b4368dc5d4c 100644 --- a/nautilus_core/indicators/src/python/momentum/psl.rs +++ b/nautilus_core/indicators/src/python/momentum/psl.rs @@ -22,8 +22,8 @@ use crate::{average::MovingAverageType, indicator::Indicator, momentum::psl::Psy #[pymethods] impl PsychologicalLine { #[new] - pub fn py_new(period: usize, ma_type: Option) -> PyResult { - Self::new(period, ma_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, ma_type: Option) -> Self { + Self::new(period, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/roc.rs b/nautilus_core/indicators/src/python/momentum/roc.rs index 871e0d155303..997cc8f53005 100644 --- a/nautilus_core/indicators/src/python/momentum/roc.rs +++ b/nautilus_core/indicators/src/python/momentum/roc.rs @@ -22,8 +22,8 @@ use crate::{average::MovingAverageType, indicator::Indicator, momentum::roc::Rat #[pymethods] impl RateOfChange { #[new] - pub fn py_new(period: usize, use_log: Option) -> PyResult { - Self::new(period, use_log).map_err(to_pyvalue_err) + pub fn py_new(period: usize, use_log: Option) -> Self { + Self::new(period, use_log) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/rsi.rs b/nautilus_core/indicators/src/python/momentum/rsi.rs index 60ba94c2ca5a..d70085f6ee28 100644 --- a/nautilus_core/indicators/src/python/momentum/rsi.rs +++ b/nautilus_core/indicators/src/python/momentum/rsi.rs @@ -27,8 +27,8 @@ use crate::{ #[pymethods] impl RelativeStrengthIndex { #[new] - pub fn py_new(period: usize, ma_type: Option) -> PyResult { - Self::new(period, ma_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, ma_type: Option) -> Self { + Self::new(period, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/stochastics.rs b/nautilus_core/indicators/src/python/momentum/stochastics.rs index de75addf833c..1ec37affff8d 100644 --- a/nautilus_core/indicators/src/python/momentum/stochastics.rs +++ b/nautilus_core/indicators/src/python/momentum/stochastics.rs @@ -22,8 +22,8 @@ use crate::{average::MovingAverageType, indicator::Indicator, momentum::stochast #[pymethods] impl Stochastics { #[new] - pub fn py_new(period_k: usize, period_d: usize) -> PyResult { - Self::new(period_k, period_d).map_err(to_pyvalue_err) + pub fn py_new(period_k: usize, period_d: usize) -> Self { + Self::new(period_k, period_d) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/swings.rs b/nautilus_core/indicators/src/python/momentum/swings.rs index 4c31e288cff1..7ba7a95e0b55 100644 --- a/nautilus_core/indicators/src/python/momentum/swings.rs +++ b/nautilus_core/indicators/src/python/momentum/swings.rs @@ -22,8 +22,8 @@ use crate::{average::MovingAverageType, indicator::Indicator, momentum::swings:: #[pymethods] impl Swings { #[new] - pub fn py_new(period: usize, ma_type: Option) -> PyResult { - Self::new(period).map_err(to_pyvalue_err) + pub fn py_new(period: usize, ma_type: Option) -> Self { + Self::new(period) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/momentum/vhf.rs b/nautilus_core/indicators/src/python/momentum/vhf.rs index 0a6b512302fd..27cab6a0fb04 100644 --- a/nautilus_core/indicators/src/python/momentum/vhf.rs +++ b/nautilus_core/indicators/src/python/momentum/vhf.rs @@ -24,8 +24,8 @@ use crate::{ #[pymethods] impl VerticalHorizontalFilter { #[new] - pub fn py_new(period: usize, ma_type: Option) -> PyResult { - Self::new(period, ma_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, ma_type: Option) -> Self { + Self::new(period, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/ratio/efficiency_ratio.rs b/nautilus_core/indicators/src/python/ratio/efficiency_ratio.rs index 6c6c523f3800..1f47aab089c7 100644 --- a/nautilus_core/indicators/src/python/ratio/efficiency_ratio.rs +++ b/nautilus_core/indicators/src/python/ratio/efficiency_ratio.rs @@ -22,8 +22,8 @@ use crate::{indicator::Indicator, ratio::efficiency_ratio::EfficiencyRatio}; #[pymethods] impl EfficiencyRatio { #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) + fn py_new(period: usize, price_type: Option) -> Self { + Self::new(period, price_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/ratio/spread_analyzer.rs b/nautilus_core/indicators/src/python/ratio/spread_analyzer.rs index 28e8267f9750..9880f5f9ae21 100644 --- a/nautilus_core/indicators/src/python/ratio/spread_analyzer.rs +++ b/nautilus_core/indicators/src/python/ratio/spread_analyzer.rs @@ -25,8 +25,8 @@ use crate::{indicator::Indicator, ratio::spread_analyzer::SpreadAnalyzer}; #[pymethods] impl SpreadAnalyzer { #[new] - fn py_new(instrument_id: InstrumentId, capacity: usize) -> PyResult { - Self::new(capacity, instrument_id).map_err(to_pyvalue_err) + fn py_new(instrument_id: InstrumentId, capacity: usize) -> Self { + Self::new(capacity, instrument_id) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/volatility/atr.rs b/nautilus_core/indicators/src/python/volatility/atr.rs index 28daaff2efef..5d598656190b 100644 --- a/nautilus_core/indicators/src/python/volatility/atr.rs +++ b/nautilus_core/indicators/src/python/volatility/atr.rs @@ -27,8 +27,8 @@ impl AverageTrueRange { ma_type: Option, use_previous: Option, value_floor: Option, - ) -> PyResult { - Self::new(period, ma_type, use_previous, value_floor).map_err(to_pyvalue_err) + ) -> Self { + Self::new(period, ma_type, use_previous, value_floor) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/volatility/dc.rs b/nautilus_core/indicators/src/python/volatility/dc.rs index e3849584b826..efa077ed296e 100644 --- a/nautilus_core/indicators/src/python/volatility/dc.rs +++ b/nautilus_core/indicators/src/python/volatility/dc.rs @@ -22,8 +22,8 @@ use crate::{average::MovingAverageType, indicator::Indicator, volatility::dc::Do #[pymethods] impl DonchianChannel { #[new] - pub fn py_new(period: usize) -> PyResult { - Self::new(period).map_err(to_pyvalue_err) + pub fn py_new(period: usize) -> Self { + Self::new(period) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/volatility/fuzzy.rs b/nautilus_core/indicators/src/python/volatility/fuzzy.rs index c6d6e8f4b9b0..72ccf34d1577 100644 --- a/nautilus_core/indicators/src/python/volatility/fuzzy.rs +++ b/nautilus_core/indicators/src/python/volatility/fuzzy.rs @@ -34,9 +34,8 @@ impl FuzzyCandle { body_size: CandleBodySize, upper_wick_size: CandleWickSize, lower_wick_size: CandleWickSize, - ) -> PyResult { + ) -> Self { Self::new(direction, size, body_size, upper_wick_size, lower_wick_size) - .map_err(to_pyvalue_err) } fn __repr__(&self) -> String { @@ -86,8 +85,8 @@ impl FuzzyCandlesticks { threshold2: f64, threshold3: f64, threshold4: f64, - ) -> PyResult { - Self::new(period, threshold1, threshold2, threshold3, threshold4).map_err(to_pyvalue_err) + ) -> Self { + Self::new(period, threshold1, threshold2, threshold3, threshold4) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/volatility/kc.rs b/nautilus_core/indicators/src/python/volatility/kc.rs index 691797fb4418..b67cb1804f95 100644 --- a/nautilus_core/indicators/src/python/volatility/kc.rs +++ b/nautilus_core/indicators/src/python/volatility/kc.rs @@ -30,7 +30,7 @@ impl KeltnerChannel { ma_type_atr: Option, use_previous: Option, atr_floor: Option, - ) -> PyResult { + ) -> Self { Self::new( period, k_multiplier, @@ -39,7 +39,6 @@ impl KeltnerChannel { use_previous, atr_floor, ) - .map_err(to_pyvalue_err) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/volatility/kp.rs b/nautilus_core/indicators/src/python/volatility/kp.rs index 16e34d8e25e5..ce3b618e8ea1 100644 --- a/nautilus_core/indicators/src/python/volatility/kp.rs +++ b/nautilus_core/indicators/src/python/volatility/kp.rs @@ -30,7 +30,7 @@ impl KeltnerPosition { ma_type_atr: Option, use_previous: Option, atr_floor: Option, - ) -> PyResult { + ) -> Self { Self::new( period, k_multiplier, @@ -39,7 +39,6 @@ impl KeltnerPosition { use_previous, atr_floor, ) - .map_err(to_pyvalue_err) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/volatility/rvi.rs b/nautilus_core/indicators/src/python/volatility/rvi.rs index 0e7b3d5842c2..2677de9c4983 100644 --- a/nautilus_core/indicators/src/python/volatility/rvi.rs +++ b/nautilus_core/indicators/src/python/volatility/rvi.rs @@ -24,12 +24,8 @@ use crate::{ #[pymethods] impl RelativeVolatilityIndex { #[new] - pub fn py_new( - period: usize, - scalar: Option, - ma_type: Option, - ) -> PyResult { - Self::new(period, scalar, ma_type).map_err(to_pyvalue_err) + pub fn py_new(period: usize, scalar: Option, ma_type: Option) -> Self { + Self::new(period, scalar, ma_type) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/python/volatility/vr.rs b/nautilus_core/indicators/src/python/volatility/vr.rs index 4082121289eb..ebb056d546ca 100644 --- a/nautilus_core/indicators/src/python/volatility/vr.rs +++ b/nautilus_core/indicators/src/python/volatility/vr.rs @@ -28,9 +28,8 @@ impl VolatilityRatio { use_previous: Option, value_floor: Option, ma_type: Option, - ) -> PyResult { + ) -> Self { Self::new(fast_period, slow_period, ma_type, use_previous, value_floor) - .map_err(to_pyvalue_err) } fn __repr__(&self) -> String { diff --git a/nautilus_core/indicators/src/ratio/efficiency_ratio.rs b/nautilus_core/indicators/src/ratio/efficiency_ratio.rs index c039c060c016..375107d16553 100644 --- a/nautilus_core/indicators/src/ratio/efficiency_ratio.rs +++ b/nautilus_core/indicators/src/ratio/efficiency_ratio.rs @@ -80,15 +80,15 @@ impl Indicator for EfficiencyRatio { impl EfficiencyRatio { /// Creates a new [`EfficiencyRatio`] instance. - pub fn new(period: usize, price_type: Option) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, price_type: Option) -> Self { + Self { period, price_type: price_type.unwrap_or(PriceType::Last), value: 0.0, inputs: Vec::with_capacity(period), deltas: Vec::with_capacity(period), initialized: false, - }) + } } pub fn update_raw(&mut self, value: f64) { diff --git a/nautilus_core/indicators/src/ratio/spread_analyzer.rs b/nautilus_core/indicators/src/ratio/spread_analyzer.rs index 19ed0e2d8e51..a60fa218fb82 100644 --- a/nautilus_core/indicators/src/ratio/spread_analyzer.rs +++ b/nautilus_core/indicators/src/ratio/spread_analyzer.rs @@ -98,8 +98,8 @@ impl Indicator for SpreadAnalyzer { impl SpreadAnalyzer { /// Creates a new [`SpreadAnalyzer`] instance. - pub fn new(capacity: usize, instrument_id: InstrumentId) -> anyhow::Result { - Ok(Self { + pub fn new(capacity: usize, instrument_id: InstrumentId) -> Self { + Self { capacity, instrument_id, current: 0.0, @@ -107,7 +107,7 @@ impl SpreadAnalyzer { initialized: false, has_inputs: false, spreads: Vec::with_capacity(capacity), - }) + } } } diff --git a/nautilus_core/indicators/src/stubs.rs b/nautilus_core/indicators/src/stubs.rs index 5b5e959a0ba8..5f34dab95fa3 100644 --- a/nautilus_core/indicators/src/stubs.rs +++ b/nautilus_core/indicators/src/stubs.rs @@ -114,54 +114,53 @@ pub fn bar_ethusdt_binance_minute_bid(#[default("1522")] close_price: &str) -> B //////////////////////////////////////////////////////////////////////////////// #[fixture] pub fn indicator_ama_10() -> AdaptiveMovingAverage { - AdaptiveMovingAverage::new(10, 2, 30, Some(PriceType::Mid)).unwrap() + AdaptiveMovingAverage::new(10, 2, 30, Some(PriceType::Mid)) } #[fixture] pub fn indicator_sma_10() -> SimpleMovingAverage { - SimpleMovingAverage::new(10, Some(PriceType::Mid)).unwrap() + SimpleMovingAverage::new(10, Some(PriceType::Mid)) } #[fixture] pub fn indicator_ema_10() -> ExponentialMovingAverage { - ExponentialMovingAverage::new(10, Some(PriceType::Mid)).unwrap() + ExponentialMovingAverage::new(10, Some(PriceType::Mid)) } #[fixture] pub fn indicator_hma_10() -> HullMovingAverage { - HullMovingAverage::new(10, Some(PriceType::Mid)).unwrap() + HullMovingAverage::new(10, Some(PriceType::Mid)) } #[fixture] pub fn indicator_rma_10() -> WilderMovingAverage { - WilderMovingAverage::new(10, Some(PriceType::Mid)).unwrap() + WilderMovingAverage::new(10, Some(PriceType::Mid)) } #[fixture] pub fn indicator_dema_10() -> DoubleExponentialMovingAverage { - DoubleExponentialMovingAverage::new(10, Some(PriceType::Mid)).unwrap() + DoubleExponentialMovingAverage::new(10, Some(PriceType::Mid)) } #[fixture] pub fn indicator_vidya_10() -> VariableIndexDynamicAverage { VariableIndexDynamicAverage::new(10, Some(PriceType::Mid), Some(MovingAverageType::Wilder)) - .unwrap() } #[fixture] pub fn indicator_vwap() -> VolumeWeightedAveragePrice { - VolumeWeightedAveragePrice::new().unwrap() + VolumeWeightedAveragePrice::new() } #[fixture] pub fn indicator_wma_10() -> WeightedMovingAverage { let weights = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; - WeightedMovingAverage::new(10, weights, Some(PriceType::Mid)).unwrap() + WeightedMovingAverage::new(10, weights, Some(PriceType::Mid)) } #[fixture] pub fn indicator_lr_10() -> LinearRegression { - LinearRegression::new(10).unwrap() + LinearRegression::new(10) } //////////////////////////////////////////////////////////////////////////////// @@ -169,12 +168,12 @@ pub fn indicator_lr_10() -> LinearRegression { //////////////////////////////////////////////////////////////////////////////// #[fixture] pub fn efficiency_ratio_10() -> EfficiencyRatio { - EfficiencyRatio::new(10, Some(PriceType::Mid)).unwrap() + EfficiencyRatio::new(10, Some(PriceType::Mid)) } #[fixture] pub fn spread_analyzer_10() -> SpreadAnalyzer { - SpreadAnalyzer::new(10, InstrumentId::from("ETHUSDT-PERP.BINANCE")).unwrap() + SpreadAnalyzer::new(10, InstrumentId::from("ETHUSDT-PERP.BINANCE")) } //////////////////////////////////////////////////////////////////////////////// @@ -182,67 +181,67 @@ pub fn spread_analyzer_10() -> SpreadAnalyzer { //////////////////////////////////////////////////////////////////////////////// #[fixture] pub fn rsi_10() -> RelativeStrengthIndex { - RelativeStrengthIndex::new(10, Some(MovingAverageType::Exponential)).unwrap() + RelativeStrengthIndex::new(10, Some(MovingAverageType::Exponential)) } #[fixture] pub fn cmo_10() -> ChandeMomentumOscillator { - ChandeMomentumOscillator::new(10, Some(MovingAverageType::Wilder)).unwrap() + ChandeMomentumOscillator::new(10, Some(MovingAverageType::Wilder)) } #[fixture] pub fn bias_10() -> Bias { - Bias::new(10, Some(MovingAverageType::Wilder)).unwrap() + Bias::new(10, Some(MovingAverageType::Wilder)) } #[fixture] pub fn vhf_10() -> VerticalHorizontalFilter { - VerticalHorizontalFilter::new(10, Some(MovingAverageType::Simple)).unwrap() + VerticalHorizontalFilter::new(10, Some(MovingAverageType::Simple)) } #[fixture] pub fn kvo_345() -> KlingerVolumeOscillator { - KlingerVolumeOscillator::new(3, 4, 5, Some(MovingAverageType::Simple)).unwrap() + KlingerVolumeOscillator::new(3, 4, 5, Some(MovingAverageType::Simple)) } #[fixture] pub fn dm_10() -> DirectionalMovement { - DirectionalMovement::new(10, Some(MovingAverageType::Simple)).unwrap() + DirectionalMovement::new(10, Some(MovingAverageType::Simple)) } #[fixture] pub fn amat_345() -> ArcherMovingAveragesTrends { - ArcherMovingAveragesTrends::new(3, 4, 5, Some(MovingAverageType::Simple)).unwrap() + ArcherMovingAveragesTrends::new(3, 4, 5, Some(MovingAverageType::Simple)) } #[fixture] pub fn swings_10() -> Swings { - Swings::new(10).unwrap() + Swings::new(10) } #[fixture] pub fn bb_10() -> BollingerBands { - BollingerBands::new(10, 0.1, Some(MovingAverageType::Simple)).unwrap() + BollingerBands::new(10, 0.1, Some(MovingAverageType::Simple)) } #[fixture] pub fn stochastics_10() -> Stochastics { - Stochastics::new(10, 10).unwrap() + Stochastics::new(10, 10) } #[fixture] pub fn psl_10() -> PsychologicalLine { - PsychologicalLine::new(10, Some(MovingAverageType::Simple)).unwrap() + PsychologicalLine::new(10, Some(MovingAverageType::Simple)) } #[fixture] pub fn pressure_10() -> Pressure { - Pressure::new(10, Some(MovingAverageType::Simple), Some(1.0)).unwrap() + Pressure::new(10, Some(MovingAverageType::Simple), Some(1.0)) } #[fixture] pub fn cci_10() -> CommodityChannelIndex { - CommodityChannelIndex::new(10, 2.0, Some(MovingAverageType::Simple)).unwrap() + CommodityChannelIndex::new(10, 2.0, Some(MovingAverageType::Simple)) } #[fixture] @@ -253,12 +252,11 @@ pub fn macd_10() -> MovingAverageConvergenceDivergence { Some(MovingAverageType::Simple), Some(PriceType::Bid), ) - .unwrap() } #[fixture] pub fn obv_10() -> OnBalanceVolume { - OnBalanceVolume::new(10).unwrap() + OnBalanceVolume::new(10) } //////////////////////////////////////////////////////////////////////////////// @@ -273,17 +271,16 @@ pub fn vr_10() -> VolatilityRatio { Some(false), Some(10.0), ) - .unwrap() } #[fixture] pub fn dc_10() -> DonchianChannel { - DonchianChannel::new(10).unwrap() + DonchianChannel::new(10) } #[fixture] pub fn rvi_10() -> RelativeVolatilityIndex { - RelativeVolatilityIndex::new(10, Some(10.0), Some(MovingAverageType::Simple)).unwrap() + RelativeVolatilityIndex::new(10, Some(10.0), Some(MovingAverageType::Simple)) } #[fixture] @@ -296,7 +293,6 @@ pub fn kc_10() -> KeltnerChannel { Some(true), Some(0.0), ) - .unwrap() } #[fixture] @@ -309,15 +305,14 @@ pub fn kp_10() -> KeltnerPosition { Some(true), Some(0.0), ) - .unwrap() } #[fixture] pub fn roc_10() -> RateOfChange { - RateOfChange::new(10, Some(true)).unwrap() + RateOfChange::new(10, Some(true)) } #[fixture] pub fn fuzzy_candlesticks_10() -> FuzzyCandlesticks { - FuzzyCandlesticks::new(10, 0.1, 0.15, 0.2, 0.3).unwrap() + FuzzyCandlesticks::new(10, 0.1, 0.15, 0.2, 0.3) } diff --git a/nautilus_core/indicators/src/testing.rs b/nautilus_core/indicators/src/testing.rs index 61d3d801d235..f2b7a8c669c2 100644 --- a/nautilus_core/indicators/src/testing.rs +++ b/nautilus_core/indicators/src/testing.rs @@ -29,6 +29,8 @@ /// # Example /// /// ``` +/// use nautilus_indicators::testing::approx_equal; +/// /// let a = 0.1 + 0.2; /// let b = 0.3; /// assert!(approx_equal(a, b)); diff --git a/nautilus_core/indicators/src/volatility/atr.rs b/nautilus_core/indicators/src/volatility/atr.rs index f8d62f9e8aa0..dd3f95858111 100644 --- a/nautilus_core/indicators/src/volatility/atr.rs +++ b/nautilus_core/indicators/src/volatility/atr.rs @@ -89,8 +89,8 @@ impl AverageTrueRange { ma_type: Option, use_previous: Option, value_floor: Option, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { period, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), use_previous: use_previous.unwrap_or(true), @@ -101,7 +101,7 @@ impl AverageTrueRange { ma: MovingAverageFactory::create(MovingAverageType::Simple, period), has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, high: f64, low: f64, close: f64) { @@ -154,33 +154,31 @@ mod tests { #[rstest] fn test_name_returns_expected_string() { - let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); assert_eq!(atr.name(), "AverageTrueRange"); } #[rstest] fn test_str_repr_returns_expected_string() { - let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), Some(true), Some(0.0)) - .unwrap(); + let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), Some(true), Some(0.0)); assert_eq!(format!("{atr}"), "AverageTrueRange(10,SIMPLE,true,0)"); } #[rstest] fn test_period() { - let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); assert_eq!(atr.period, 10); } #[rstest] fn test_initialized_without_inputs_returns_false() { - let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); assert!(!atr.initialized()); } #[rstest] fn test_initialized_with_required_inputs_returns_true() { - let mut atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let mut atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); for _ in 0..10 { atr.update_raw(1.0, 1.0, 1.0); } @@ -189,14 +187,13 @@ mod tests { #[rstest] fn test_value_with_no_inputs_returns_zero() { - let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); assert_eq!(atr.value, 0.0); } #[rstest] fn test_value_with_epsilon_input() { - let mut atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let mut atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); let epsilon = std::f64::EPSILON; atr.update_raw(epsilon, epsilon, epsilon); assert_eq!(atr.value, 0.0); @@ -204,24 +201,21 @@ mod tests { #[rstest] fn test_value_with_one_ones_input() { - let mut atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let mut atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); atr.update_raw(1.0, 1.0, 1.0); assert_eq!(atr.value, 0.0); } #[rstest] fn test_value_with_one_input() { - let mut atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let mut atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); atr.update_raw(1.00020, 1.0, 1.00010); assert!(approx_equal(atr.value, 0.0002)); } #[rstest] fn test_value_with_three_inputs() { - let mut atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let mut atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); atr.update_raw(1.00020, 1.0, 1.00010); atr.update_raw(1.00020, 1.0, 1.00010); atr.update_raw(1.00020, 1.0, 1.00010); @@ -230,8 +224,7 @@ mod tests { #[rstest] fn test_value_with_close_on_high() { - let mut atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let mut atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); let mut high = 1.00010; let mut low = 1.0; for _ in 0..1000 { @@ -245,8 +238,7 @@ mod tests { #[rstest] fn test_value_with_close_on_low() { - let mut atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let mut atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); let mut high = 1.00010; let mut low = 1.0; for _ in 0..1000 { @@ -262,7 +254,7 @@ mod tests { fn test_floor_with_ten_ones_inputs() { let floor = 0.00005; let mut floored_atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, Some(floor)).unwrap(); + AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, Some(floor)); for _ in 0..20 { floored_atr.update_raw(1.0, 1.0, 1.0); } @@ -273,7 +265,7 @@ mod tests { fn test_floor_with_exponentially_decreasing_high_inputs() { let floor = 0.00005; let mut floored_atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, Some(floor)).unwrap(); + AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, Some(floor)); let mut high = 1.00020; let low = 1.0; let close = 1.0; @@ -286,8 +278,7 @@ mod tests { #[rstest] fn test_reset_successfully_returns_indicator_to_fresh_state() { - let mut atr = - AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap(); + let mut atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None); for _ in 0..1000 { atr.update_raw(1.00010, 1.0, 1.00005); } diff --git a/nautilus_core/indicators/src/volatility/dc.rs b/nautilus_core/indicators/src/volatility/dc.rs index 997cf6a56054..a825f7e27790 100644 --- a/nautilus_core/indicators/src/volatility/dc.rs +++ b/nautilus_core/indicators/src/volatility/dc.rs @@ -75,8 +75,8 @@ impl Indicator for DonchianChannel { impl DonchianChannel { /// Creates a new [`DonchianChannel`] instance. - pub fn new(period: usize) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize) -> Self { + Self { period, upper: 0.0, middle: 0.0, @@ -85,7 +85,7 @@ impl DonchianChannel { lower_prices: VecDeque::with_capacity(period), has_inputs: false, initialized: false, - }) + } } pub fn update_raw(&mut self, high: f64, low: f64) { diff --git a/nautilus_core/indicators/src/volatility/fuzzy.rs b/nautilus_core/indicators/src/volatility/fuzzy.rs index 31de59fc63d2..95c8a57e8477 100644 --- a/nautilus_core/indicators/src/volatility/fuzzy.rs +++ b/nautilus_core/indicators/src/volatility/fuzzy.rs @@ -117,14 +117,14 @@ impl FuzzyCandle { body_size: CandleBodySize, upper_wick_size: CandleWickSize, lower_wick_size: CandleWickSize, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { direction, size, body_size, upper_wick_size, lower_wick_size, - }) + } } } @@ -218,8 +218,8 @@ impl FuzzyCandlesticks { threshold2: f64, threshold3: f64, threshold4: f64, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { period, threshold1, threshold2, @@ -232,8 +232,7 @@ impl FuzzyCandlesticks { CandleBodySize::None, CandleWickSize::None, CandleWickSize::None, - ) - .unwrap(), + ), has_inputs: false, initialized: false, lengths: VecDeque::with_capacity(period), @@ -244,7 +243,7 @@ impl FuzzyCandlesticks { last_high: 0.0, last_low: 0.0, last_close: 0.0, - }) + } } pub fn update_raw(&mut self, open: f64, high: f64, low: f64, close: f64) { @@ -308,8 +307,7 @@ impl FuzzyCandlesticks { mean_lower_wick_percent, sd_lower_wick_percent, ), - ) - .unwrap(); + ); self.vector = vec![ self.value.direction as i32, @@ -331,8 +329,7 @@ impl FuzzyCandlesticks { CandleBodySize::None, CandleWickSize::None, CandleWickSize::None, - ) - .unwrap(); + ); self.vector = Vec::new(); self.last_open = 0.0; self.last_high = 0.0; diff --git a/nautilus_core/indicators/src/volatility/kc.rs b/nautilus_core/indicators/src/volatility/kc.rs index ce765574f769..b2117010afc8 100644 --- a/nautilus_core/indicators/src/volatility/kc.rs +++ b/nautilus_core/indicators/src/volatility/kc.rs @@ -88,8 +88,8 @@ impl KeltnerChannel { ma_type_atr: Option, use_previous: Option, atr_floor: Option, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { period, k_multiplier, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), @@ -102,8 +102,8 @@ impl KeltnerChannel { has_inputs: false, initialized: false, ma: MovingAverageFactory::create(ma_type.unwrap_or(MovingAverageType::Simple), period), - atr: AverageTrueRange::new(period, ma_type_atr, use_previous, atr_floor)?, - }) + atr: AverageTrueRange::new(period, ma_type_atr, use_previous, atr_floor), + } } pub fn update_raw(&mut self, high: f64, low: f64, close: f64) { diff --git a/nautilus_core/indicators/src/volatility/kp.rs b/nautilus_core/indicators/src/volatility/kp.rs index 48109031229c..3424475fb27f 100644 --- a/nautilus_core/indicators/src/volatility/kp.rs +++ b/nautilus_core/indicators/src/volatility/kp.rs @@ -88,8 +88,8 @@ impl KeltnerPosition { ma_type_atr: Option, use_previous: Option, atr_floor: Option, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { period, k_multiplier, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), @@ -106,9 +106,8 @@ impl KeltnerPosition { ma_type_atr, use_previous, atr_floor, - ) - .unwrap(), - }) + ), + } } pub fn update_raw(&mut self, high: f64, low: f64, close: f64) { diff --git a/nautilus_core/indicators/src/volatility/rvi.rs b/nautilus_core/indicators/src/volatility/rvi.rs index 4431aa02b74c..71e6aa1cbcb8 100644 --- a/nautilus_core/indicators/src/volatility/rvi.rs +++ b/nautilus_core/indicators/src/volatility/rvi.rs @@ -93,12 +93,8 @@ impl Indicator for RelativeVolatilityIndex { impl RelativeVolatilityIndex { /// Creates a new [`RelativeVolatilityIndex`] instance. - pub fn new( - period: usize, - scalar: Option, - ma_type: Option, - ) -> anyhow::Result { - Ok(Self { + pub fn new(period: usize, scalar: Option, ma_type: Option) -> Self { + Self { period, scalar: scalar.unwrap_or(100.0), ma_type: ma_type.unwrap_or(MovingAverageType::Simple), @@ -117,7 +113,7 @@ impl RelativeVolatilityIndex { previous_close: 0.0, std: 0.0, has_inputs: false, - }) + } } pub fn update_raw(&mut self, close: f64) { diff --git a/nautilus_core/indicators/src/volatility/vr.rs b/nautilus_core/indicators/src/volatility/vr.rs index f7c40fe32178..934db2e50fc7 100644 --- a/nautilus_core/indicators/src/volatility/vr.rs +++ b/nautilus_core/indicators/src/volatility/vr.rs @@ -85,8 +85,8 @@ impl VolatilityRatio { ma_type: Option, use_previous: Option, value_floor: Option, - ) -> anyhow::Result { - Ok(Self { + ) -> Self { + Self { fast_period, slow_period, ma_type: ma_type.unwrap_or(MovingAverageType::Simple), @@ -95,9 +95,9 @@ impl VolatilityRatio { value: 0.0, has_inputs: false, initialized: false, - atr_fast: AverageTrueRange::new(fast_period, ma_type, use_previous, value_floor)?, - atr_slow: AverageTrueRange::new(slow_period, ma_type, use_previous, value_floor)?, - }) + atr_fast: AverageTrueRange::new(fast_period, ma_type, use_previous, value_floor), + atr_slow: AverageTrueRange::new(slow_period, ma_type, use_previous, value_floor), + } } pub fn update_raw(&mut self, high: f64, low: f64, close: f64) { From add1085d38c16f40be452dd30dfbe1d6d12c9afe Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 19 Aug 2024 07:36:12 +1000 Subject: [PATCH 69/72] Fix clippy lints --- nautilus_core/backtest/src/exchange.rs | 2 +- nautilus_core/backtest/src/matching_engine.rs | 5 +++-- nautilus_core/common/src/timer.rs | 2 ++ nautilus_core/indicators/src/average/ama.rs | 1 + nautilus_core/indicators/src/average/dema.rs | 1 + nautilus_core/indicators/src/average/ema.rs | 1 + nautilus_core/indicators/src/average/hma.rs | 1 + nautilus_core/indicators/src/average/lr.rs | 1 + nautilus_core/indicators/src/average/rma.rs | 1 + nautilus_core/indicators/src/average/sma.rs | 1 + nautilus_core/indicators/src/average/vidya.rs | 1 + nautilus_core/indicators/src/average/vwap.rs | 1 + nautilus_core/indicators/src/average/wma.rs | 3 ++- nautilus_core/indicators/src/book/imbalance.rs | 1 + nautilus_core/indicators/src/momentum/amat.rs | 1 + nautilus_core/indicators/src/momentum/aroon.rs | 1 + nautilus_core/indicators/src/momentum/bb.rs | 1 + nautilus_core/indicators/src/momentum/bias.rs | 1 + nautilus_core/indicators/src/momentum/cci.rs | 1 + nautilus_core/indicators/src/momentum/cmo.rs | 1 + nautilus_core/indicators/src/momentum/dm.rs | 1 + nautilus_core/indicators/src/momentum/kvo.rs | 1 + nautilus_core/indicators/src/momentum/macd.rs | 1 + nautilus_core/indicators/src/momentum/obv.rs | 1 + nautilus_core/indicators/src/momentum/pressure.rs | 1 + nautilus_core/indicators/src/momentum/psl.rs | 1 + nautilus_core/indicators/src/momentum/roc.rs | 1 + nautilus_core/indicators/src/momentum/rsi.rs | 1 + nautilus_core/indicators/src/momentum/stochastics.rs | 1 + nautilus_core/indicators/src/momentum/swings.rs | 1 + nautilus_core/indicators/src/momentum/vhf.rs | 1 + nautilus_core/indicators/src/ratio/efficiency_ratio.rs | 1 + nautilus_core/indicators/src/ratio/spread_analyzer.rs | 1 + nautilus_core/indicators/src/volatility/atr.rs | 1 + nautilus_core/indicators/src/volatility/dc.rs | 1 + nautilus_core/indicators/src/volatility/fuzzy.rs | 2 ++ nautilus_core/indicators/src/volatility/kc.rs | 1 + nautilus_core/indicators/src/volatility/kp.rs | 1 + nautilus_core/indicators/src/volatility/rvi.rs | 1 + nautilus_core/indicators/src/volatility/vr.rs | 1 + 40 files changed, 45 insertions(+), 4 deletions(-) diff --git a/nautilus_core/backtest/src/exchange.rs b/nautilus_core/backtest/src/exchange.rs index 8c403c7f5c35..a69a1ec7b086 100644 --- a/nautilus_core/backtest/src/exchange.rs +++ b/nautilus_core/backtest/src/exchange.rs @@ -103,7 +103,7 @@ impl SimulatedExchange { anyhow::bail!("single-currency account has multiple starting currencies") } // TODO register and load modules - Ok(SimulatedExchange { + Ok(Self { id: venue, oms_type, account_type, diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index ffcda05b2fc9..b249d93b63f4 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -66,7 +66,8 @@ pub struct OrderMatchingEngineConfig { } impl OrderMatchingEngineConfig { - pub fn new( + #[must_use] + pub const fn new( bar_execution: bool, reject_stop_orders: bool, support_gtd_orders: bool, @@ -76,9 +77,9 @@ impl OrderMatchingEngineConfig { use_reduce_only: bool, ) -> Self { Self { - support_gtd_orders, bar_execution, reject_stop_orders, + support_gtd_orders, support_contingent_orders, use_position_ids, use_random_ids, diff --git a/nautilus_core/common/src/timer.rs b/nautilus_core/common/src/timer.rs index e698f30b3a8a..7fdc3a50ff0c 100644 --- a/nautilus_core/common/src/timer.rs +++ b/nautilus_core/common/src/timer.rs @@ -135,6 +135,7 @@ pub struct TestTimer { impl TestTimer { /// Creates a new [`TestTimer`] instance. + #[must_use] pub fn new( name: &str, interval_ns: u64, @@ -238,6 +239,7 @@ pub struct LiveTimer { impl LiveTimer { /// Creates a new [`LiveTimer`] instance. + #[must_use] pub fn new( name: &str, interval_ns: u64, diff --git a/nautilus_core/indicators/src/average/ama.rs b/nautilus_core/indicators/src/average/ama.rs index 892668f817f2..2600b1e30ad0 100644 --- a/nautilus_core/indicators/src/average/ama.rs +++ b/nautilus_core/indicators/src/average/ama.rs @@ -105,6 +105,7 @@ impl Indicator for AdaptiveMovingAverage { impl AdaptiveMovingAverage { /// Creates a new [`AdaptiveMovingAverage`] instance. + #[must_use] pub fn new( period_efficiency_ratio: usize, period_fast: usize, diff --git a/nautilus_core/indicators/src/average/dema.rs b/nautilus_core/indicators/src/average/dema.rs index 43c6b3511a7a..981871fe9ae2 100644 --- a/nautilus_core/indicators/src/average/dema.rs +++ b/nautilus_core/indicators/src/average/dema.rs @@ -88,6 +88,7 @@ impl Indicator for DoubleExponentialMovingAverage { impl DoubleExponentialMovingAverage { /// Creates a new [`DoubleExponentialMovingAverage`] instance. + #[must_use] pub fn new(period: usize, price_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/average/ema.rs b/nautilus_core/indicators/src/average/ema.rs index e2c919a6a007..72c3fbfbd91e 100644 --- a/nautilus_core/indicators/src/average/ema.rs +++ b/nautilus_core/indicators/src/average/ema.rs @@ -79,6 +79,7 @@ impl Indicator for ExponentialMovingAverage { impl ExponentialMovingAverage { /// Creates a new [`ExponentialMovingAverage`] instance. + #[must_use] pub fn new(period: usize, price_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/average/hma.rs b/nautilus_core/indicators/src/average/hma.rs index a72a5e06482c..69ab215c559f 100644 --- a/nautilus_core/indicators/src/average/hma.rs +++ b/nautilus_core/indicators/src/average/hma.rs @@ -97,6 +97,7 @@ fn _get_weights(size: usize) -> Vec { impl HullMovingAverage { /// Creates a new [`HullMovingAverage`] instance. + #[must_use] pub fn new(period: usize, price_type: Option) -> Self { let period_halved = period / 2; let period_sqrt = (period as f64).sqrt() as usize; diff --git a/nautilus_core/indicators/src/average/lr.rs b/nautilus_core/indicators/src/average/lr.rs index e9c2aa7996f4..5e55edcb4320 100644 --- a/nautilus_core/indicators/src/average/lr.rs +++ b/nautilus_core/indicators/src/average/lr.rs @@ -76,6 +76,7 @@ impl Indicator for LinearRegression { impl LinearRegression { /// Creates a new [`LinearRegression`] instance. + #[must_use] pub fn new(period: usize) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/average/rma.rs b/nautilus_core/indicators/src/average/rma.rs index 154f41f1d679..c9d5dbe682c9 100644 --- a/nautilus_core/indicators/src/average/rma.rs +++ b/nautilus_core/indicators/src/average/rma.rs @@ -79,6 +79,7 @@ impl Indicator for WilderMovingAverage { impl WilderMovingAverage { /// Creates a new [`WilderMovingAverage`] instance. + #[must_use] pub fn new(period: usize, price_type: Option) -> Self { // The Wilder Moving Average is The Wilder's Moving Average is simply // an Exponential Moving Average (EMA) with a modified alpha. diff --git a/nautilus_core/indicators/src/average/sma.rs b/nautilus_core/indicators/src/average/sma.rs index 8fd6311ac3e0..0d87277239f6 100644 --- a/nautilus_core/indicators/src/average/sma.rs +++ b/nautilus_core/indicators/src/average/sma.rs @@ -78,6 +78,7 @@ impl Indicator for SimpleMovingAverage { impl SimpleMovingAverage { /// Creates a new [`SimpleMovingAverage`] instance. + #[must_use] pub fn new(period: usize, price_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/average/vidya.rs b/nautilus_core/indicators/src/average/vidya.rs index 4e1d424e0e01..5be0b3c006cf 100644 --- a/nautilus_core/indicators/src/average/vidya.rs +++ b/nautilus_core/indicators/src/average/vidya.rs @@ -87,6 +87,7 @@ impl Indicator for VariableIndexDynamicAverage { impl VariableIndexDynamicAverage { /// Creates a new [`VariableIndexDynamicAverage`] instance. + #[must_use] pub fn new( period: usize, price_type: Option, diff --git a/nautilus_core/indicators/src/average/vwap.rs b/nautilus_core/indicators/src/average/vwap.rs index 65ae5e64b856..0407c8012f12 100644 --- a/nautilus_core/indicators/src/average/vwap.rs +++ b/nautilus_core/indicators/src/average/vwap.rs @@ -72,6 +72,7 @@ impl Indicator for VolumeWeightedAveragePrice { impl VolumeWeightedAveragePrice { /// Creates a new [`VolumeWeightedAveragePrice`] instance. + #[must_use] pub const fn new() -> Self { Self { value: 0.0, diff --git a/nautilus_core/indicators/src/average/wma.rs b/nautilus_core/indicators/src/average/wma.rs index 6c99b4e0375b..b642f25263cf 100644 --- a/nautilus_core/indicators/src/average/wma.rs +++ b/nautilus_core/indicators/src/average/wma.rs @@ -53,6 +53,7 @@ impl Display for WeightedMovingAverage { impl WeightedMovingAverage { /// Creates a new [`WeightedMovingAverage`] instance. + #[must_use] pub fn new(period: usize, weights: Vec, price_type: Option) -> Self { assert_eq!( weights.len(), @@ -169,7 +170,7 @@ mod tests { #[rstest] #[should_panic] fn test_different_weights_len_and_period_error() { - WeightedMovingAverage::new(10, vec![0.5, 0.5, 0.5], None); + let _ = WeightedMovingAverage::new(10, vec![0.5, 0.5, 0.5], None); } #[rstest] diff --git a/nautilus_core/indicators/src/book/imbalance.rs b/nautilus_core/indicators/src/book/imbalance.rs index c02b1ce3cb5f..73f72fce10b7 100644 --- a/nautilus_core/indicators/src/book/imbalance.rs +++ b/nautilus_core/indicators/src/book/imbalance.rs @@ -65,6 +65,7 @@ impl Indicator for BookImbalanceRatio { impl BookImbalanceRatio { /// Creates a new [`BookImbalanceRatio`] instance. + #[must_use] pub const fn new() -> Self { Self { value: 0.0, diff --git a/nautilus_core/indicators/src/momentum/amat.rs b/nautilus_core/indicators/src/momentum/amat.rs index f943ebf75f01..f14ef5dd6c54 100644 --- a/nautilus_core/indicators/src/momentum/amat.rs +++ b/nautilus_core/indicators/src/momentum/amat.rs @@ -91,6 +91,7 @@ impl Indicator for ArcherMovingAveragesTrends { impl ArcherMovingAveragesTrends { /// Creates a new [`ArcherMovingAveragesTrends`] instance. + #[must_use] pub fn new( fast_period: usize, slow_period: usize, diff --git a/nautilus_core/indicators/src/momentum/aroon.rs b/nautilus_core/indicators/src/momentum/aroon.rs index 2ef5e7e103ee..68fe2b466100 100644 --- a/nautilus_core/indicators/src/momentum/aroon.rs +++ b/nautilus_core/indicators/src/momentum/aroon.rs @@ -92,6 +92,7 @@ impl Indicator for AroonOscillator { impl AroonOscillator { /// Creates a new [`AroonOscillator`] instance. + #[must_use] pub fn new(period: usize) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/bb.rs b/nautilus_core/indicators/src/momentum/bb.rs index 4335660cbdfc..2bc498bfce45 100644 --- a/nautilus_core/indicators/src/momentum/bb.rs +++ b/nautilus_core/indicators/src/momentum/bb.rs @@ -99,6 +99,7 @@ impl Indicator for BollingerBands { impl BollingerBands { /// Creates a new [`BollingerBands`] instance. + #[must_use] pub fn new(period: usize, k: f64, ma_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/bias.rs b/nautilus_core/indicators/src/momentum/bias.rs index b3891980ba6f..3f2b36c07b13 100644 --- a/nautilus_core/indicators/src/momentum/bias.rs +++ b/nautilus_core/indicators/src/momentum/bias.rs @@ -73,6 +73,7 @@ impl Indicator for Bias { impl Bias { /// Creates a new [`Bias`] instance. + #[must_use] pub fn new(period: usize, ma_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/cci.rs b/nautilus_core/indicators/src/momentum/cci.rs index 2e0399b187a0..e609d77a577b 100644 --- a/nautilus_core/indicators/src/momentum/cci.rs +++ b/nautilus_core/indicators/src/momentum/cci.rs @@ -78,6 +78,7 @@ impl Indicator for CommodityChannelIndex { impl CommodityChannelIndex { /// Creates a new [`CommodityChannelIndex`] instance. + #[must_use] pub fn new(period: usize, scalar: f64, ma_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/cmo.rs b/nautilus_core/indicators/src/momentum/cmo.rs index b59796d1aa9c..7969cc23fcf5 100644 --- a/nautilus_core/indicators/src/momentum/cmo.rs +++ b/nautilus_core/indicators/src/momentum/cmo.rs @@ -82,6 +82,7 @@ impl Indicator for ChandeMomentumOscillator { impl ChandeMomentumOscillator { /// Creates a new [`ChandeMomentumOscillator`] instance. + #[must_use] pub fn new(period: usize, ma_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/dm.rs b/nautilus_core/indicators/src/momentum/dm.rs index b71776eb5ca3..687d59a979fe 100644 --- a/nautilus_core/indicators/src/momentum/dm.rs +++ b/nautilus_core/indicators/src/momentum/dm.rs @@ -78,6 +78,7 @@ impl Indicator for DirectionalMovement { impl DirectionalMovement { /// Creates a new [`DirectionalMovement`] instance. + #[must_use] pub fn new(period: usize, ma_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/kvo.rs b/nautilus_core/indicators/src/momentum/kvo.rs index a4dad03a4625..8e7a2c919082 100644 --- a/nautilus_core/indicators/src/momentum/kvo.rs +++ b/nautilus_core/indicators/src/momentum/kvo.rs @@ -93,6 +93,7 @@ impl Indicator for KlingerVolumeOscillator { impl KlingerVolumeOscillator { /// Creates a new [`KlingerVolumeOscillator`] instance. + #[must_use] pub fn new( fast_period: usize, slow_period: usize, diff --git a/nautilus_core/indicators/src/momentum/macd.rs b/nautilus_core/indicators/src/momentum/macd.rs index 0d9f77fb8399..4ab4207766bd 100644 --- a/nautilus_core/indicators/src/momentum/macd.rs +++ b/nautilus_core/indicators/src/momentum/macd.rs @@ -94,6 +94,7 @@ impl Indicator for MovingAverageConvergenceDivergence { impl MovingAverageConvergenceDivergence { /// Creates a new [`MovingAverageConvergenceDivergence`] instance. + #[must_use] pub fn new( fast_period: usize, slow_period: usize, diff --git a/nautilus_core/indicators/src/momentum/obv.rs b/nautilus_core/indicators/src/momentum/obv.rs index 4e4c1acc21bf..9a8421550378 100644 --- a/nautilus_core/indicators/src/momentum/obv.rs +++ b/nautilus_core/indicators/src/momentum/obv.rs @@ -73,6 +73,7 @@ impl Indicator for OnBalanceVolume { impl OnBalanceVolume { /// Creates a new [`OnBalanceVolume`] instance. + #[must_use] pub fn new(period: usize) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/pressure.rs b/nautilus_core/indicators/src/momentum/pressure.rs index c947ef18b0df..c06ce6bc1286 100644 --- a/nautilus_core/indicators/src/momentum/pressure.rs +++ b/nautilus_core/indicators/src/momentum/pressure.rs @@ -81,6 +81,7 @@ impl Indicator for Pressure { impl Pressure { /// Creates a new [`Pressure`] instance. + #[must_use] pub fn new(period: usize, ma_type: Option, atr_floor: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/psl.rs b/nautilus_core/indicators/src/momentum/psl.rs index 4ef7bb7ac5c5..e9ab80cd1f1b 100644 --- a/nautilus_core/indicators/src/momentum/psl.rs +++ b/nautilus_core/indicators/src/momentum/psl.rs @@ -74,6 +74,7 @@ impl Indicator for PsychologicalLine { impl PsychologicalLine { /// Creates a new [`PsychologicalLine`] instance. + #[must_use] pub fn new(period: usize, ma_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/roc.rs b/nautilus_core/indicators/src/momentum/roc.rs index 06d7baf8862b..e81b7406b847 100644 --- a/nautilus_core/indicators/src/momentum/roc.rs +++ b/nautilus_core/indicators/src/momentum/roc.rs @@ -70,6 +70,7 @@ impl Indicator for RateOfChange { impl RateOfChange { /// Creates a new [`RateOfChange`] instance. + #[must_use] pub fn new(period: usize, use_log: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/rsi.rs b/nautilus_core/indicators/src/momentum/rsi.rs index 556a1c4bb22d..09c1b67ab170 100644 --- a/nautilus_core/indicators/src/momentum/rsi.rs +++ b/nautilus_core/indicators/src/momentum/rsi.rs @@ -87,6 +87,7 @@ impl Indicator for RelativeStrengthIndex { impl RelativeStrengthIndex { /// Creates a new [`RelativeStrengthIndex`] instance. + #[must_use] pub fn new(period: usize, ma_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/stochastics.rs b/nautilus_core/indicators/src/momentum/stochastics.rs index 41e63a4f1f22..0690aef4aec0 100644 --- a/nautilus_core/indicators/src/momentum/stochastics.rs +++ b/nautilus_core/indicators/src/momentum/stochastics.rs @@ -78,6 +78,7 @@ impl Indicator for Stochastics { impl Stochastics { /// Creates a new [`Stochastics`] instance. + #[must_use] pub fn new(period_k: usize, period_d: usize) -> Self { Self { period_k, diff --git a/nautilus_core/indicators/src/momentum/swings.rs b/nautilus_core/indicators/src/momentum/swings.rs index 46492b3b561d..ffa694b06b7e 100644 --- a/nautilus_core/indicators/src/momentum/swings.rs +++ b/nautilus_core/indicators/src/momentum/swings.rs @@ -89,6 +89,7 @@ impl Indicator for Swings { impl Swings { /// Creates a new [`Swings`] instance. + #[must_use] pub fn new(period: usize) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/momentum/vhf.rs b/nautilus_core/indicators/src/momentum/vhf.rs index 7925abb9002d..bb5506b6fc6f 100644 --- a/nautilus_core/indicators/src/momentum/vhf.rs +++ b/nautilus_core/indicators/src/momentum/vhf.rs @@ -77,6 +77,7 @@ impl Indicator for VerticalHorizontalFilter { impl VerticalHorizontalFilter { /// Creates a new [`VerticalHorizontalFilter`] instance. + #[must_use] pub fn new(period: usize, ma_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/ratio/efficiency_ratio.rs b/nautilus_core/indicators/src/ratio/efficiency_ratio.rs index 375107d16553..5dd8a42afae4 100644 --- a/nautilus_core/indicators/src/ratio/efficiency_ratio.rs +++ b/nautilus_core/indicators/src/ratio/efficiency_ratio.rs @@ -80,6 +80,7 @@ impl Indicator for EfficiencyRatio { impl EfficiencyRatio { /// Creates a new [`EfficiencyRatio`] instance. + #[must_use] pub fn new(period: usize, price_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/ratio/spread_analyzer.rs b/nautilus_core/indicators/src/ratio/spread_analyzer.rs index a60fa218fb82..8bcf2d78fcaf 100644 --- a/nautilus_core/indicators/src/ratio/spread_analyzer.rs +++ b/nautilus_core/indicators/src/ratio/spread_analyzer.rs @@ -98,6 +98,7 @@ impl Indicator for SpreadAnalyzer { impl SpreadAnalyzer { /// Creates a new [`SpreadAnalyzer`] instance. + #[must_use] pub fn new(capacity: usize, instrument_id: InstrumentId) -> Self { Self { capacity, diff --git a/nautilus_core/indicators/src/volatility/atr.rs b/nautilus_core/indicators/src/volatility/atr.rs index dd3f95858111..3785f70f289d 100644 --- a/nautilus_core/indicators/src/volatility/atr.rs +++ b/nautilus_core/indicators/src/volatility/atr.rs @@ -84,6 +84,7 @@ impl Indicator for AverageTrueRange { impl AverageTrueRange { /// Creates a new [`AverageTrueRange`] instance. + #[must_use] pub fn new( period: usize, ma_type: Option, diff --git a/nautilus_core/indicators/src/volatility/dc.rs b/nautilus_core/indicators/src/volatility/dc.rs index a825f7e27790..0eaac1b0e55c 100644 --- a/nautilus_core/indicators/src/volatility/dc.rs +++ b/nautilus_core/indicators/src/volatility/dc.rs @@ -75,6 +75,7 @@ impl Indicator for DonchianChannel { impl DonchianChannel { /// Creates a new [`DonchianChannel`] instance. + #[must_use] pub fn new(period: usize) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/volatility/fuzzy.rs b/nautilus_core/indicators/src/volatility/fuzzy.rs index 95c8a57e8477..6de99b4c7272 100644 --- a/nautilus_core/indicators/src/volatility/fuzzy.rs +++ b/nautilus_core/indicators/src/volatility/fuzzy.rs @@ -111,6 +111,7 @@ impl Display for FuzzyCandle { } impl FuzzyCandle { + #[must_use] pub const fn new( direction: CandleDirection, size: CandleSize, @@ -212,6 +213,7 @@ impl FuzzyCandlesticks { /// - Threshold2: f64 : The membership function x threshold2 (> threshold1). /// - Threshold3: f64 : The membership function x threshold3 (> threshold2). /// - Threshold4: f64 : The membership function x threshold4 (> threshold3). + #[must_use] pub fn new( period: usize, threshold1: f64, diff --git a/nautilus_core/indicators/src/volatility/kc.rs b/nautilus_core/indicators/src/volatility/kc.rs index b2117010afc8..e563a9309310 100644 --- a/nautilus_core/indicators/src/volatility/kc.rs +++ b/nautilus_core/indicators/src/volatility/kc.rs @@ -81,6 +81,7 @@ impl Indicator for KeltnerChannel { impl KeltnerChannel { /// Creates a new [`KeltnerChannel`] instance. + #[must_use] pub fn new( period: usize, k_multiplier: f64, diff --git a/nautilus_core/indicators/src/volatility/kp.rs b/nautilus_core/indicators/src/volatility/kp.rs index 3424475fb27f..71232f9474a0 100644 --- a/nautilus_core/indicators/src/volatility/kp.rs +++ b/nautilus_core/indicators/src/volatility/kp.rs @@ -81,6 +81,7 @@ impl Indicator for KeltnerPosition { impl KeltnerPosition { /// Creates a new [`KeltnerPosition`] instance. + #[must_use] pub fn new( period: usize, k_multiplier: f64, diff --git a/nautilus_core/indicators/src/volatility/rvi.rs b/nautilus_core/indicators/src/volatility/rvi.rs index 71e6aa1cbcb8..9df277b3804b 100644 --- a/nautilus_core/indicators/src/volatility/rvi.rs +++ b/nautilus_core/indicators/src/volatility/rvi.rs @@ -93,6 +93,7 @@ impl Indicator for RelativeVolatilityIndex { impl RelativeVolatilityIndex { /// Creates a new [`RelativeVolatilityIndex`] instance. + #[must_use] pub fn new(period: usize, scalar: Option, ma_type: Option) -> Self { Self { period, diff --git a/nautilus_core/indicators/src/volatility/vr.rs b/nautilus_core/indicators/src/volatility/vr.rs index 934db2e50fc7..0cf1e14158d1 100644 --- a/nautilus_core/indicators/src/volatility/vr.rs +++ b/nautilus_core/indicators/src/volatility/vr.rs @@ -79,6 +79,7 @@ impl Indicator for VolatilityRatio { impl VolatilityRatio { /// Creates a new [`VolatilityRatio`] instance. + #[must_use] pub fn new( fast_period: usize, slow_period: usize, From fbaa2b4c38c612a799132514395b6fac4111aded Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 19 Aug 2024 07:39:05 +1000 Subject: [PATCH 70/72] Update release notes --- RELEASES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 6dacd8ab5ba6..e889d16520ca 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -7,7 +7,7 @@ Released on TBD (UTC). - Added `LogLevel::TRACE` (only available in Rust for debug/development builds) - Added `Actor.subscribe_signal(...)` method and `Data.is_signal(...)` class method (#1853), thanks @faysou - Added Binance Futures support for `HEDGE` mode (#1846), thanks @DevRoss -- Overhauled and refined error modeling and handling in Rust (#1849), thanks @twitu +- Overhauled and refined error modeling and handling in Rust (#1849, #1858), thanks @twitu - Improved `BinanceExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improved `BybitExecutionClient` position report requests (can now filter by instrument and includes reporting for flat positions) - Improved `LiveExecutionEngine` reconciliation robustness and recovery when internal positions do not match external positions @@ -26,7 +26,7 @@ Released on TBD (UTC). - Moved `snapshot_positions_interval_secs` config setting to `ExecEngineConfig` (can now be used for all environment contexts) ### Fixes -- Fixed `Position` exception type on duplicate fill (should be `KeyError` like `Order`) +- Fixed `Position` exception type on duplicate fill (should be `KeyError` to align with the same error for `Order`) - Fixed Bybit position report parsing when position is flat (`BybitPositionSide` now correctly handles the empty string) --- From 6dda4b185fd9e70785fc4143ed3c8231fc243729 Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 19 Aug 2024 17:21:53 +1000 Subject: [PATCH 71/72] Improve WeightedMovingAverage condition check --- nautilus_core/indicators/src/average/wma.rs | 11 ++++++----- .../indicators/src/python/average/wma.rs | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/nautilus_core/indicators/src/average/wma.rs b/nautilus_core/indicators/src/average/wma.rs index b642f25263cf..91df3ba28e21 100644 --- a/nautilus_core/indicators/src/average/wma.rs +++ b/nautilus_core/indicators/src/average/wma.rs @@ -15,6 +15,7 @@ use std::fmt::Display; +use nautilus_core::correctness::{check_predicate_true, FAILED}; use nautilus_model::{ data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, enums::PriceType, @@ -55,11 +56,11 @@ impl WeightedMovingAverage { /// Creates a new [`WeightedMovingAverage`] instance. #[must_use] pub fn new(period: usize, weights: Vec, price_type: Option) -> Self { - assert_eq!( - weights.len(), - period, - "Weights length must be equal to period" - ); + check_predicate_true( + period == weights.len(), + "`period` must be equal to `weights` length", + ) + .expect(FAILED); Self { period, weights, diff --git a/nautilus_core/indicators/src/python/average/wma.rs b/nautilus_core/indicators/src/python/average/wma.rs index d14f8870ba88..06766acf0e6e 100644 --- a/nautilus_core/indicators/src/python/average/wma.rs +++ b/nautilus_core/indicators/src/python/average/wma.rs @@ -13,7 +13,7 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- -use nautilus_core::python::to_pyvalue_err; +use nautilus_core::{correctness::check_predicate_true, python::to_pyvalue_err}; use nautilus_model::{ data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, enums::PriceType, @@ -28,8 +28,17 @@ use crate::{ #[pymethods] impl WeightedMovingAverage { #[new] - pub fn py_new(period: usize, weights: Vec, price_type: Option) -> Self { - Self::new(period, weights, price_type) + pub fn py_new( + period: usize, + weights: Vec, + price_type: Option, + ) -> PyResult { + check_predicate_true( + period == weights.len(), + "`period` must be equal to `weights` length", + ) + .map_err(to_pyvalue_err); + Ok(Self::new(period, weights, price_type)) } fn __repr__(&self) -> String { From 05346c4ed9139c8ef7c272b486df9a93892b2faf Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 19 Aug 2024 17:24:47 +1000 Subject: [PATCH 72/72] Update dependencies and release notes --- RELEASES.md | 2 +- nautilus_core/Cargo.lock | 4 +- poetry.lock | 105 +++++++++++++++++++++------------------ pyproject.toml | 4 +- 4 files changed, 61 insertions(+), 54 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index e889d16520ca..a186c06963be 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,6 @@ # NautilusTrader 1.199.0 Beta -Released on TBD (UTC). +Released on 19th August 2024 (UTC). ### Enhancements - Added `LiveExecEngineConfig.generate_missing_orders` reconciliation config option to align internal and external position states diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index 29914eca0537..77e9b25f79a1 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -2428,9 +2428,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.156" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5f43f184355eefb8d17fc948dbecf6c13be3c141f20d834ae842193a448c72a" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" diff --git a/poetry.lock b/poetry.lock index ad0051b03126..e6d03b8da6f1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1249,56 +1249,63 @@ files = [ [[package]] name = "numpy" -version = "2.0.1" +version = "2.1.0" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"}, - {file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"}, - {file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"}, - {file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"}, - {file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}, - {file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}, - {file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"}, - {file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"}, - {file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"}, - {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, +python-versions = ">=3.10" +files = [ + {file = "numpy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6326ab99b52fafdcdeccf602d6286191a79fe2fda0ae90573c5814cd2b0bc1b8"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0937e54c09f7a9a68da6889362ddd2ff584c02d015ec92672c099b61555f8911"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:30014b234f07b5fec20f4146f69e13cfb1e33ee9a18a1879a0142fbb00d47673"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:899da829b362ade41e1e7eccad2cf274035e1cb36ba73034946fccd4afd8606b"}, + {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08801848a40aea24ce16c2ecde3b756f9ad756586fb2d13210939eb69b023f5b"}, + {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:398049e237d1aae53d82a416dade04defed1a47f87d18d5bd615b6e7d7e41d1f"}, + {file = "numpy-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0abb3916a35d9090088a748636b2c06dc9a6542f99cd476979fb156a18192b84"}, + {file = "numpy-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10e2350aea18d04832319aac0f887d5fcec1b36abd485d14f173e3e900b83e33"}, + {file = "numpy-2.1.0-cp310-cp310-win32.whl", hash = "sha256:f6b26e6c3b98adb648243670fddc8cab6ae17473f9dc58c51574af3e64d61211"}, + {file = "numpy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:f505264735ee074250a9c78247ee8618292091d9d1fcc023290e9ac67e8f1afa"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:76368c788ccb4f4782cf9c842b316140142b4cbf22ff8db82724e82fe1205dce"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f8e93a01a35be08d31ae33021e5268f157a2d60ebd643cfc15de6ab8e4722eb1"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9523f8b46485db6939bd069b28b642fec86c30909cea90ef550373787f79530e"}, + {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54139e0eb219f52f60656d163cbe67c31ede51d13236c950145473504fa208cb"}, + {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebbf9fbdabed208d4ecd2e1dfd2c0741af2f876e7ae522c2537d404ca895c3"}, + {file = "numpy-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:378cb4f24c7d93066ee4103204f73ed046eb88f9ad5bb2275bb9fa0f6a02bd36"}, + {file = "numpy-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8f699a709120b220dfe173f79c73cb2a2cab2c0b88dd59d7b49407d032b8ebd"}, + {file = "numpy-2.1.0-cp311-cp311-win32.whl", hash = "sha256:ffbd6faeb190aaf2b5e9024bac9622d2ee549b7ec89ef3a9373fa35313d44e0e"}, + {file = "numpy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0af3a5987f59d9c529c022c8c2a64805b339b7ef506509fba7d0556649b9714b"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fe76d75b345dc045acdbc006adcb197cc680754afd6c259de60d358d60c93736"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f358ea9e47eb3c2d6eba121ab512dfff38a88db719c38d1e67349af210bc7529"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:dd94ce596bda40a9618324547cfaaf6650b1a24f5390350142499aa4e34e53d1"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b47c551c6724960479cefd7353656498b86e7232429e3a41ab83be4da1b109e8"}, + {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0756a179afa766ad7cb6f036de622e8a8f16ffdd55aa31f296c870b5679d745"}, + {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24003ba8ff22ea29a8c306e61d316ac74111cebf942afbf692df65509a05f111"}, + {file = "numpy-2.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b34fa5e3b5d6dc7e0a4243fa0f81367027cb6f4a7215a17852979634b5544ee0"}, + {file = "numpy-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4f982715e65036c34897eb598d64aef15150c447be2cfc6643ec7a11af06574"}, + {file = "numpy-2.1.0-cp312-cp312-win32.whl", hash = "sha256:c4cd94dfefbefec3f8b544f61286584292d740e6e9d4677769bc76b8f41deb02"}, + {file = "numpy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0cdef204199278f5c461a0bed6ed2e052998276e6d8ab2963d5b5c39a0500bc"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8ab81ccd753859ab89e67199b9da62c543850f819993761c1e94a75a814ed667"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442596f01913656d579309edcd179a2a2f9977d9a14ff41d042475280fc7f34e"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:848c6b5cad9898e4b9ef251b6f934fa34630371f2e916261070a4eb9092ffd33"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:54c6a63e9d81efe64bfb7bcb0ec64332a87d0b87575f6009c8ba67ea6374770b"}, + {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:652e92fc409e278abdd61e9505649e3938f6d04ce7ef1953f2ec598a50e7c195"}, + {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab32eb9170bf8ffcbb14f11613f4a0b108d3ffee0832457c5d4808233ba8977"}, + {file = "numpy-2.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8fb49a0ba4d8f41198ae2d52118b050fd34dace4b8f3fb0ee34e23eb4ae775b1"}, + {file = "numpy-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44e44973262dc3ae79e9063a1284a73e09d01b894b534a769732ccd46c28cc62"}, + {file = "numpy-2.1.0-cp313-cp313-win32.whl", hash = "sha256:ab83adc099ec62e044b1fbb3a05499fa1e99f6d53a1dde102b2d85eff66ed324"}, + {file = "numpy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:de844aaa4815b78f6023832590d77da0e3b6805c644c33ce94a1e449f16d6ab5"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:343e3e152bf5a087511cd325e3b7ecfd5b92d369e80e74c12cd87826e263ec06"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f07fa2f15dabe91259828ce7d71b5ca9e2eb7c8c26baa822c825ce43552f4883"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5474dad8c86ee9ba9bb776f4b99ef2d41b3b8f4e0d199d4f7304728ed34d0300"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1f817c71683fd1bb5cff1529a1d085a57f02ccd2ebc5cd2c566f9a01118e3b7d"}, + {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a3336fbfa0d38d3deacd3fe7f3d07e13597f29c13abf4d15c3b6dc2291cbbdd"}, + {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a894c51fd8c4e834f00ac742abad73fc485df1062f1b875661a3c1e1fb1c2f6"}, + {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:9156ca1f79fc4acc226696e95bfcc2b486f165a6a59ebe22b2c1f82ab190384a"}, + {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:624884b572dff8ca8f60fab591413f077471de64e376b17d291b19f56504b2bb"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15ef8b2177eeb7e37dd5ef4016f30b7659c57c2c0b57a779f1d537ff33a72c7b"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e5f0642cdf4636198a4990de7a71b693d824c56a757862230454629cf62e323d"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15976718c004466406342789f31b6673776360f3b1e3c575f25302d7e789575"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6c1de77ded79fef664d5098a66810d4d27ca0224e9051906e634b3f7ead134c2"}, + {file = "numpy-2.1.0.tar.gz", hash = "sha256:7dc90da0081f7e1da49ec4e398ede6a8e9cc4f5ebe5f9e06b443ed889ee9aaa2"}, ] [[package]] @@ -2398,4 +2405,4 @@ ib = ["async-timeout", "defusedxml", "nautilus_ibapi"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "f24652140907b098e8c95633d6a14225c27e221f770271215072ea58d59164a5" +content-hash = "41104c42827b0180e4696888f137310d98c985cf4bdd77156a286873cbb85a5e" diff --git a/pyproject.toml b/pyproject.toml index 60130f757c4a..0ece24c50e48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ include = [ requires = [ "setuptools", "poetry-core>=1.9.0", - "numpy>=2.0.1", + "numpy>=2.1.0", "Cython==3.0.11", "toml>=0.10.2", ] @@ -52,7 +52,7 @@ generate-setup-file = false [tool.poetry.dependencies] python = ">=3.10,<3.13" cython = "==3.0.11" # Build dependency (pinned for stability) -numpy = "^2.0.1" # Build dependency +numpy = "^2.1.0" # Build dependency setuptools = ">=72" # Build dependency toml = "^0.10.2" # Build dependency click = "^8.1.7"