diff --git a/nautilus_core/Cargo.lock b/nautilus_core/Cargo.lock index 118ae0c5486e..93b966678745 100644 --- a/nautilus_core/Cargo.lock +++ b/nautilus_core/Cargo.lock @@ -2128,6 +2128,12 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + [[package]] name = "ipnet" version = "2.9.0" @@ -3287,6 +3293,7 @@ checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" dependencies = [ "cfg-if", "indoc", + "inventory", "libc", "memoffset", "parking_lot", diff --git a/nautilus_core/accounting/Cargo.toml b/nautilus_core/accounting/Cargo.toml index b844912c1a71..0c3bf7a33c60 100644 --- a/nautilus_core/accounting/Cargo.toml +++ b/nautilus_core/accounting/Cargo.toml @@ -11,8 +11,8 @@ name = "nautilus_accounting" crate-type = ["rlib", "cdylib"] [dependencies] -nautilus-common = { path = "../common", features = ["stubs"] } -nautilus-model = { path = "../model", features = ["stubs"]} +nautilus-common = { path = "../common", features = ["stubs"]} +nautilus-model = { path = "../model" } nautilus-core = { path = "../core" } anyhow = { workspace = true } pyo3 = { workspace = true, optional = true } @@ -21,16 +21,17 @@ serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -rstest.workspace = true +rstest = { workspace = true } + +[build-dependencies] +cbindgen = { workspace = true, optional = true } [features] extension-module = [ "pyo3/extension-module", "nautilus-core/extension-module", + "nautilus-model/extension-module", "nautilus-common/extension-module", ] python = ["pyo3"] -default = ["python"] - -[build-dependencies] -cbindgen = { workspace = true, optional = true } +default = [] diff --git a/nautilus_core/accounting/src/account/base.rs b/nautilus_core/accounting/src/account/base.rs index 676da4702831..fcfaf6163d95 100644 --- a/nautilus_core/accounting/src/account/base.rs +++ b/nautilus_core/accounting/src/account/base.rs @@ -34,7 +34,6 @@ use rust_decimal::prelude::ToPrimitive; pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model") )] pub struct BaseAccount { - #[pyo3(get)] pub id: AccountId, pub account_type: AccountType, pub base_currency: Option, diff --git a/nautilus_core/accounting/src/python/cash.rs b/nautilus_core/accounting/src/python/cash.rs index 43481bb44a1d..86a63f96f738 100644 --- a/nautilus_core/accounting/src/python/cash.rs +++ b/nautilus_core/accounting/src/python/cash.rs @@ -78,6 +78,12 @@ impl CashAccount { ) } + #[getter] + #[pyo3(name = "id")] + fn py_id(&self) -> AccountId { + self.id + } + #[getter] #[pyo3(name = "base_currency")] fn py_base_currency(&self) -> Option { diff --git a/nautilus_core/adapters/Cargo.toml b/nautilus_core/adapters/Cargo.toml index 427fdbb08715..b1f39b9bb196 100644 --- a/nautilus_core/adapters/Cargo.toml +++ b/nautilus_core/adapters/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["rlib", "staticlib", "cdylib"] [dependencies] nautilus-common = { path = "../common" } nautilus-core = { path = "../core" } -nautilus-model = { path = "../model", features = ["stubs"]} +nautilus-model = { path = "../model", features = ["stubs"] } anyhow = { workspace = true } chrono = { workspace = true } indexmap = { workspace = true } @@ -35,6 +35,10 @@ dbn = { version = "0.15.1", optional = true, features = ["python"] } streaming-iterator = "0.1.9" time = "0.3.31" +[dev-dependencies] +criterion = { workspace = true } +rstest = { workspace = true } + [features] extension-module = [ "pyo3/extension-module", @@ -42,10 +46,13 @@ extension-module = [ "nautilus-core/extension-module", "nautilus-model/extension-module", ] -databento = ["dep:databento", "dbn"] -python = ["pyo3", "pyo3-asyncio"] -default = ["databento", "python"] - -[dev-dependencies] -criterion = { workspace = true } -rstest = { workspace = true } +databento = ["dep:databento", "dbn", "python"] +ffi = ["nautilus-core/ffi", "nautilus-model/ffi", "nautilus-common/ffi"] +python = [ + "pyo3", + "pyo3-asyncio", + "nautilus-core/python", + "nautilus-model/python", + "nautilus-common/python", +] +default = ["ffi", "python"] diff --git a/nautilus_core/backtest/Cargo.toml b/nautilus_core/backtest/Cargo.toml index 1ea8e22a31a5..9091b2caa12b 100644 --- a/nautilus_core/backtest/Cargo.toml +++ b/nautilus_core/backtest/Cargo.toml @@ -21,6 +21,9 @@ ustr = { workspace = true } tempfile = { workspace = true } rstest = { workspace = true} +[build-dependencies] +cbindgen = { workspace = true, optional = true } + [features] extension-module = [ "pyo3/extension-module", @@ -28,9 +31,6 @@ extension-module = [ "nautilus-core/extension-module", "nautilus-model/extension-module", ] -ffi = ["cbindgen"] -python = ["pyo3"] +ffi = ["cbindgen", "nautilus-core/ffi", "nautilus-common/ffi"] +python = ["pyo3", "nautilus-core/python", "nautilus-common/python"] default = ["ffi", "python"] - -[build-dependencies] -cbindgen = { workspace = true, optional = true } diff --git a/nautilus_core/backtest/build.rs b/nautilus_core/backtest/build.rs index 3b1c048c551a..ba9ce9b1c8f2 100644 --- a/nautilus_core/backtest/build.rs +++ b/nautilus_core/backtest/build.rs @@ -13,29 +13,34 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- -extern crate cbindgen; - -use std::{env, path::PathBuf}; +use std::env; #[allow(clippy::expect_used)] // OK in build script fn main() { - let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let _is_ffi_feature_on = env::var("CARGO_FEATURE_FFI").is_ok(); + + #[cfg(feature = "ffi")] + if !_is_ffi_feature_on { + extern crate cbindgen; + use std::path::PathBuf; + let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - // Generate C headers - let config_c = cbindgen::Config::from_file("cbindgen.toml") - .expect("unable to find cbindgen.toml configuration file"); + // Generate C headers + let config_c = cbindgen::Config::from_file("cbindgen.toml") + .expect("unable to find cbindgen.toml configuration file"); - let c_header_path = crate_dir.join("../../nautilus_trader/core/includes/backtest.h"); - cbindgen::generate_with_config(&crate_dir, config_c) - .expect("unable to generate bindings") - .write_to_file(c_header_path); + let c_header_path = crate_dir.join("../../nautilus_trader/core/includes/backtest.h"); + cbindgen::generate_with_config(&crate_dir, config_c) + .expect("unable to generate bindings") + .write_to_file(c_header_path); - // Generate Cython definitions - let config_cython = cbindgen::Config::from_file("cbindgen_cython.toml") - .expect("unable to find cbindgen_cython.toml configuration file"); + // Generate Cython definitions + let config_cython = cbindgen::Config::from_file("cbindgen_cython.toml") + .expect("unable to find cbindgen_cython.toml configuration file"); - let cython_path = crate_dir.join("../../nautilus_trader/core/rust/backtest.pxd"); - cbindgen::generate_with_config(&crate_dir, config_cython) - .expect("unable to generate bindings") - .write_to_file(cython_path); + let cython_path = crate_dir.join("../../nautilus_trader/core/rust/backtest.pxd"); + cbindgen::generate_with_config(&crate_dir, config_cython) + .expect("unable to generate bindings") + .write_to_file(cython_path); + } } diff --git a/nautilus_core/backtest/src/engine.rs b/nautilus_core/backtest/src/engine.rs index f7d489fbb725..777292f36eba 100644 --- a/nautilus_core/backtest/src/engine.rs +++ b/nautilus_core/backtest/src/engine.rs @@ -61,7 +61,6 @@ impl Default for TimeEventAccumulator { //////////////////////////////////////////////////////////////////////////////// // C API //////////////////////////////////////////////////////////////////////////////// -#[cfg(feature = "ffi")] #[repr(C)] pub struct TimeEventAccumulatorAPI(Box); diff --git a/nautilus_core/common/Cargo.toml b/nautilus_core/common/Cargo.toml index 76fb5a4507c1..96a582fa91d7 100644 --- a/nautilus_core/common/Cargo.toml +++ b/nautilus_core/common/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["rlib", "staticlib"] [dependencies] nautilus-core = { path = "../core" } -nautilus-model = { path = "../model", features = ["stubs"]} +nautilus-model = { path = "../model" } anyhow = { workspace = true } chrono = { workspace = true } indexmap = { workspace = true } @@ -32,19 +32,20 @@ sysinfo = "0.30.5" tracing-subscriber = { version = "0.3.18", default-features = false, features = ["smallvec", "fmt", "ansi", "std", "env-filter"] } [dev-dependencies] +rstest = { workspace = true } tempfile = { workspace = true } +[build-dependencies] +cbindgen = { workspace = true, optional = true } + [features] extension-module = [ "pyo3/extension-module", "nautilus-core/extension-module", "nautilus-model/extension-module", ] -ffi = ["cbindgen"] -python = ["pyo3", "pyo3-asyncio"] -stubs = ["rstest"] +ffi = ["cbindgen", "nautilus-core/ffi", "nautilus-model/ffi"] +python = ["pyo3", "pyo3-asyncio", "nautilus-core/python", "nautilus-model/python"] +stubs = ["rstest", "nautilus-model/stubs"] redis = ["dep:redis"] -default = ["ffi", "python", "redis"] - -[build-dependencies] -cbindgen = { workspace = true, optional = true } +default = [] diff --git a/nautilus_core/common/build.rs b/nautilus_core/common/build.rs index 5e20ed2f406c..d79b79012eeb 100644 --- a/nautilus_core/common/build.rs +++ b/nautilus_core/common/build.rs @@ -13,48 +13,53 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- -extern crate cbindgen; - -use std::{ - env, - fs::File, - io::{Read, Write}, - path::PathBuf, -}; +use std::env; #[allow(clippy::expect_used)] // OK in build script fn main() { - let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - - // Generate C headers - let config_c = cbindgen::Config::from_file("cbindgen.toml") - .expect("unable to find cbindgen.toml configuration file"); - - let c_header_path = crate_dir.join("../../nautilus_trader/core/includes/common.h"); - cbindgen::generate_with_config(&crate_dir, config_c) - .expect("unable to generate bindings") - .write_to_file(c_header_path); - - // Generate Cython definitions - let config_cython = cbindgen::Config::from_file("cbindgen_cython.toml") - .expect("unable to find cbindgen_cython.toml configuration file"); - - let cython_path = crate_dir.join("../../nautilus_trader/core/rust/common.pxd"); - cbindgen::generate_with_config(&crate_dir, config_cython) - .expect("unable to generate bindings") - .write_to_file(cython_path.clone()); - - // Open and read the file entirely - let mut src = File::open(cython_path.clone()).expect("`File::open` failed"); - let mut data = String::new(); - src.read_to_string(&mut data) - .expect("invalid UTF-8 in stream"); - - // Run the replace operation in memory - let new_data = data.replace("cdef enum", "cpdef enum"); - - // Recreate the file and dump the processed contents to it - let mut dst = File::create(cython_path).expect("`File::create` failed"); - dst.write_all(new_data.as_bytes()) - .expect("I/O error on `dist.write`"); + let _is_ffi_feature_on = env::var("CARGO_FEATURE_FFI").is_ok(); + + #[cfg(feature = "ffi")] + if !_is_ffi_feature_on { + extern crate cbindgen; + use std::{ + fs::File, + io::{Read, Write}, + path::PathBuf, + }; + + let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + // Generate C headers + let config_c = cbindgen::Config::from_file("cbindgen.toml") + .expect("unable to find cbindgen.toml configuration file"); + + let c_header_path = crate_dir.join("../../nautilus_trader/core/includes/common.h"); + cbindgen::generate_with_config(&crate_dir, config_c) + .expect("unable to generate bindings") + .write_to_file(c_header_path); + + // Generate Cython definitions + let config_cython = cbindgen::Config::from_file("cbindgen_cython.toml") + .expect("unable to find cbindgen_cython.toml configuration file"); + + let cython_path = crate_dir.join("../../nautilus_trader/core/rust/common.pxd"); + cbindgen::generate_with_config(&crate_dir, config_cython) + .expect("unable to generate bindings") + .write_to_file(cython_path.clone()); + + // Open and read the file entirely + let mut src = File::open(cython_path.clone()).expect("`File::open` failed"); + let mut data = String::new(); + src.read_to_string(&mut data) + .expect("invalid UTF-8 in stream"); + + // Run the replace operation in memory + let new_data = data.replace("cdef enum", "cpdef enum"); + + // Recreate the file and dump the processed contents to it + let mut dst = File::create(cython_path).expect("`File::create` failed"); + dst.write_all(new_data.as_bytes()) + .expect("I/O error on `dist.write`"); + } } diff --git a/nautilus_core/core/Cargo.toml b/nautilus_core/core/Cargo.toml index fd232d3a5598..ffc67a0da24c 100644 --- a/nautilus_core/core/Cargo.toml +++ b/nautilus_core/core/Cargo.toml @@ -21,12 +21,6 @@ ustr = { workspace = true } uuid = { workspace = true } heck = "0.4.1" -[features] -extension-module = ["pyo3/extension-module"] -ffi = ["cbindgen"] -python = ["pyo3"] -default = ["ffi", "python"] - [dev-dependencies] criterion = { workspace = true } iai = { workspace = true } @@ -34,3 +28,10 @@ rstest = { workspace = true } [build-dependencies] cbindgen = { workspace = true, optional = true } + +[features] +extension-module = ["pyo3/extension-module"] +ffi = ["cbindgen"] +python = ["pyo3"] +default = [] + diff --git a/nautilus_core/core/build.rs b/nautilus_core/core/build.rs index fc97a8613cf2..303705664fd2 100644 --- a/nautilus_core/core/build.rs +++ b/nautilus_core/core/build.rs @@ -13,48 +13,52 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- -extern crate cbindgen; - -use std::{ - env, - fs::File, - io::{Read, Write}, - path::PathBuf, -}; +use std::env; #[allow(clippy::expect_used)] // OK in build script fn main() { - let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - - // Generate C headers - let config_c = cbindgen::Config::from_file("cbindgen.toml") - .expect("unable to find cbindgen.toml configuration file"); - - let c_header_path = crate_dir.join("../../nautilus_trader/core/includes/core.h"); - cbindgen::generate_with_config(&crate_dir, config_c) - .expect("unable to generate bindings") - .write_to_file(c_header_path); - - // Generate Cython definitions - let config_cython = cbindgen::Config::from_file("cbindgen_cython.toml") - .expect("unable to find cbindgen_cython.toml configuration file"); - - let cython_path = crate_dir.join("../../nautilus_trader/core/rust/core.pxd"); - cbindgen::generate_with_config(&crate_dir, config_cython) - .expect("unable to generate bindings") - .write_to_file(cython_path.clone()); - - // Open and read the file entirely - let mut src = File::open(cython_path.clone()).expect("`File::open` failed"); - let mut data = String::new(); - src.read_to_string(&mut data) - .expect("invalid UTF-8 in stream"); - - // Run the replace operation in memory - let new_data = data.replace("cdef enum", "cpdef enum"); - - // Recreate the file and dump the processed contents to it - let mut dst = File::create(cython_path).expect("`File::create` failed"); - dst.write_all(new_data.as_bytes()) - .expect("I/O error on `dist.write`"); + let _is_ffi_feature_on = env::var("CARGO_FEATURE_FFI").is_ok(); + + #[cfg(feature = "ffi")] + if !_is_ffi_feature_on { + extern crate cbindgen; + use std::{ + fs::File, + io::{Read, Write}, + path::PathBuf, + }; + let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + // Generate C headers + let config_c = cbindgen::Config::from_file("cbindgen.toml") + .expect("unable to find cbindgen.toml configuration file"); + + let c_header_path = crate_dir.join("../../nautilus_trader/core/includes/core.h"); + cbindgen::generate_with_config(&crate_dir, config_c) + .expect("unable to generate bindings") + .write_to_file(c_header_path); + + // Generate Cython definitions + let config_cython = cbindgen::Config::from_file("cbindgen_cython.toml") + .expect("unable to find cbindgen_cython.toml configuration file"); + + let cython_path = crate_dir.join("../../nautilus_trader/core/rust/core.pxd"); + cbindgen::generate_with_config(&crate_dir, config_cython) + .expect("unable to generate bindings") + .write_to_file(cython_path.clone()); + + // Open and read the file entirely + let mut src = File::open(cython_path.clone()).expect("`File::open` failed"); + let mut data = String::new(); + src.read_to_string(&mut data) + .expect("invalid UTF-8 in stream"); + + // Run the replace operation in memory + let new_data = data.replace("cdef enum", "cpdef enum"); + + // Recreate the file and dump the processed contents to it + let mut dst = File::create(cython_path).expect("`File::create` failed"); + dst.write_all(new_data.as_bytes()) + .expect("I/O error on `dist.write`"); + } } diff --git a/nautilus_core/indicators/Cargo.toml b/nautilus_core/indicators/Cargo.toml index b7ad21487269..1e00e35ffc6c 100644 --- a/nautilus_core/indicators/Cargo.toml +++ b/nautilus_core/indicators/Cargo.toml @@ -18,7 +18,7 @@ pyo3 = { workspace = true, optional = true } strum = { workspace = true } [dev-dependencies] -rstest.workspace = true +rstest = { workspace = true } [features] extension-module = [ @@ -27,4 +27,4 @@ extension-module = [ "nautilus-model/extension-module", ] python = ["pyo3"] -default = ["python"] +default = [] diff --git a/nautilus_core/infrastructure/Cargo.toml b/nautilus_core/infrastructure/Cargo.toml index 1450eb6f2603..886209f35292 100644 --- a/nautilus_core/infrastructure/Cargo.toml +++ b/nautilus_core/infrastructure/Cargo.toml @@ -11,9 +11,9 @@ name = "nautilus_infrastructure" crate-type = ["rlib", "cdylib"] [dependencies] -nautilus-common = { path = "../common" } -nautilus-core = { path = "../core" } -nautilus-model = { path = "../model" } +nautilus-common = { path = "../common", features = ["redis"] } +nautilus-core = { path = "../core" , features = ["python"] } +nautilus-model = { path = "../model" , features = ["python"] } anyhow = { workspace = true } pyo3 = { workspace = true, optional = true } redis = { workspace = true, optional = true } @@ -21,7 +21,7 @@ rmp-serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -rstest.workspace = true +rstest = { workspace = true } [features] extension-module = [ @@ -32,4 +32,4 @@ extension-module = [ ] python = ["pyo3"] redis = ["dep:redis"] -default = ["python", "redis"] +default = ["redis"] diff --git a/nautilus_core/model/Cargo.toml b/nautilus_core/model/Cargo.toml index eba1f63c494f..79cf51e5ddf2 100644 --- a/nautilus_core/model/Cargo.toml +++ b/nautilus_core/model/Cargo.toml @@ -30,17 +30,6 @@ derive_builder = "0.13.1" evalexpr = "11.3.0" tabled = "0.15.0" -[features] -extension-module = [ - "pyo3/extension-module", - "nautilus-core/extension-module", -] -ffi = ["cbindgen"] -python = ["pyo3"] -stubs = ["rstest"] -trivial_copy = [] # Enables deriving the `Copy` trait for data types (should be included in default) -default = ["ffi", "python", "stubs", "trivial_copy"] - [dev-dependencies] criterion = { workspace = true } float-cmp = { workspace = true } @@ -49,6 +38,17 @@ iai = { workspace = true } [build-dependencies] cbindgen = { workspace = true, optional = true } +[features] +extension-module = [ + "pyo3/extension-module", + "nautilus-core/extension-module", +] +ffi = ["cbindgen", "nautilus-core/ffi"] +python = ["pyo3", "pyo3/multiple-pymethods", "nautilus-core/python"] +stubs = ["rstest"] +trivial_copy = [] # Enables deriving the `Copy` trait for data types (should be included in default) +default = ["trivial_copy"] + [[bench]] name = "criterion_fixed_precision_benchmark" harness = false diff --git a/nautilus_core/model/build.rs b/nautilus_core/model/build.rs index c925769bfabb..53419b1b2488 100644 --- a/nautilus_core/model/build.rs +++ b/nautilus_core/model/build.rs @@ -13,10 +13,9 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- -extern crate cbindgen; - +use std::env; +#[cfg(feature = "ffi")] use std::{ - env, fs::File, io::{Read, Write}, path::PathBuf, @@ -24,37 +23,44 @@ use std::{ #[allow(clippy::expect_used)] // OK in build script fn main() { - let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - - // Generate C headers - let config_c = cbindgen::Config::from_file("cbindgen.toml") - .expect("unable to find cbindgen.toml configuration file"); - - let c_header_path = crate_dir.join("../../nautilus_trader/core/includes/model.h"); - cbindgen::generate_with_config(&crate_dir, config_c) - .expect("unable to generate bindings") - .write_to_file(c_header_path); - - // Generate Cython definitions - let config_cython = cbindgen::Config::from_file("cbindgen_cython.toml") - .expect("unable to find cbindgen_cython.toml configuration file"); - - let cython_path = crate_dir.join("../../nautilus_trader/core/rust/model.pxd"); - cbindgen::generate_with_config(&crate_dir, config_cython) - .expect("unable to generate bindings") - .write_to_file(cython_path.clone()); - - // Open and read the file entirely - let mut src = File::open(cython_path.clone()).expect("`File::open` failed"); - let mut data = String::new(); - src.read_to_string(&mut data) - .expect("invalid UTF-8 in stream"); - - // Run the replace operation in memory - let new_data = data.replace("cdef enum", "cpdef enum"); - - // Recreate the file and dump the processed contents to it - let mut dst = File::create(cython_path).expect("`File::create` failed"); - dst.write_all(new_data.as_bytes()) - .expect("I/O error on `dist.write`"); + let _is_ffi_feature_on = env::var("CARGO_FEATURE_FFI").is_ok(); + + #[cfg(feature = "ffi")] + if !_is_ffi_feature_on { + extern crate cbindgen; + + let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + // Generate C headers + let config_c = cbindgen::Config::from_file("cbindgen.toml") + .expect("unable to find cbindgen.toml configuration file"); + + let c_header_path = crate_dir.join("../../nautilus_trader/core/includes/model.h"); + cbindgen::generate_with_config(&crate_dir, config_c) + .expect("unable to generate bindings") + .write_to_file(c_header_path); + + // Generate Cython definitions + let config_cython = cbindgen::Config::from_file("cbindgen_cython.toml") + .expect("unable to find cbindgen_cython.toml configuration file"); + + let cython_path = crate_dir.join("../../nautilus_trader/core/rust/model.pxd"); + cbindgen::generate_with_config(&crate_dir, config_cython) + .expect("unable to generate bindings") + .write_to_file(cython_path.clone()); + + // Open and read the file entirely + let mut src = File::open(cython_path.clone()).expect("`File::open` failed"); + let mut data = String::new(); + src.read_to_string(&mut data) + .expect("invalid UTF-8 in stream"); + + // Run the replace operation in memory + let new_data = data.replace("cdef enum", "cpdef enum"); + + // Recreate the file and dump the processed contents to it + let mut dst = File::create(cython_path).expect("`File::create` failed"); + dst.write_all(new_data.as_bytes()) + .expect("I/O error on `dist.write`"); + } } diff --git a/nautilus_core/model/src/data/bar.rs b/nautilus_core/model/src/data/bar.rs index c1f4d0ef890e..2f983094278e 100644 --- a/nautilus_core/model/src/data/bar.rs +++ b/nautilus_core/model/src/data/bar.rs @@ -22,7 +22,8 @@ use std::{ use indexmap::IndexMap; use nautilus_core::{serialization::Serializable, time::UnixNanos}; -use pyo3::prelude::*; +#[cfg(feature = "python")] +use pyo3::{PyAny, PyResult}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{ diff --git a/nautilus_core/model/src/data/delta.rs b/nautilus_core/model/src/data/delta.rs index 5886fa3830ff..c9276837f217 100644 --- a/nautilus_core/model/src/data/delta.rs +++ b/nautilus_core/model/src/data/delta.rs @@ -17,20 +17,14 @@ use std::{ collections::HashMap, fmt::{Display, Formatter}, hash::Hash, - str::FromStr, }; use indexmap::IndexMap; -use nautilus_core::{python::to_pyvalue_err, serialization::Serializable, time::UnixNanos}; -use pyo3::prelude::*; +use nautilus_core::{serialization::Serializable, time::UnixNanos}; use serde::{Deserialize, Serialize}; -use super::order::{BookOrder, OrderId, NULL_ORDER}; -use crate::{ - enums::{BookAction, FromU8, OrderSide}, - identifiers::instrument_id::InstrumentId, - types::{price::Price, quantity::Quantity}, -}; +use super::order::{BookOrder, NULL_ORDER}; +use crate::{enums::BookAction, identifiers::instrument_id::InstrumentId}; /// Represents a single change/delta in an order book. #[repr(C)] @@ -128,61 +122,6 @@ impl OrderBookDelta { metadata.insert("ts_init".to_string(), "UInt64".to_string()); metadata } - - /// Create a new [`OrderBookDelta`] extracted from the given [`PyAny`]. - pub fn from_pyobject(obj: &PyAny) -> PyResult { - let instrument_id_obj: &PyAny = obj.getattr("instrument_id")?.extract()?; - let instrument_id_str = instrument_id_obj.getattr("value")?.extract()?; - let instrument_id = InstrumentId::from_str(instrument_id_str) - .map_err(to_pyvalue_err) - .unwrap(); - - let action_obj: &PyAny = obj.getattr("action")?.extract()?; - let action_u8 = action_obj.getattr("value")?.extract()?; - let action = BookAction::from_u8(action_u8).unwrap(); - - let flags: u8 = obj.getattr("flags")?.extract()?; - let sequence: u64 = obj.getattr("sequence")?.extract()?; - let ts_event: UnixNanos = obj.getattr("ts_event")?.extract()?; - let ts_init: UnixNanos = obj.getattr("ts_init")?.extract()?; - - let order_pyobject = obj.getattr("order")?; - let order: BookOrder = if order_pyobject.is_none() { - NULL_ORDER - } else { - let side_obj: &PyAny = order_pyobject.getattr("side")?.extract()?; - let side_u8 = side_obj.getattr("value")?.extract()?; - let side = OrderSide::from_u8(side_u8).unwrap(); - - 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 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 order_id: OrderId = order_pyobject.getattr("order_id")?.extract()?; - BookOrder { - side, - price, - size, - order_id, - } - }; - - Ok(Self::new( - instrument_id, - action, - order, - flags, - sequence, - ts_event, - ts_init, - )) - } } impl Display for OrderBookDelta { @@ -210,7 +149,8 @@ impl Serializable for OrderBookDelta {} pub mod stubs { use rstest::fixture; - use super::{BookAction, BookOrder, OrderBookDelta, OrderSide}; + use super::{BookAction, BookOrder, OrderBookDelta}; + use crate::enums::OrderSide; use crate::{ identifiers::instrument_id::InstrumentId, types::{price::Price, quantity::Quantity}, @@ -247,9 +187,14 @@ pub mod stubs { //////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { + use nautilus_core::serialization::Serializable; use rstest::rstest; - use super::{stubs::*, *}; + use crate::data::delta::OrderBookDelta; + use crate::data::order::BookOrder; + use crate::data::stubs::*; + use crate::enums::BookAction; + use crate::identifiers::instrument_id::InstrumentId; use crate::{ enums::OrderSide, types::{price::Price, quantity::Quantity}, diff --git a/nautilus_core/model/src/data/mod.rs b/nautilus_core/model/src/data/mod.rs index d70561d533c5..ce7f45eda6cd 100644 --- a/nautilus_core/model/src/data/mod.rs +++ b/nautilus_core/model/src/data/mod.rs @@ -19,6 +19,8 @@ pub mod deltas; pub mod depth; pub mod order; pub mod quote; +#[cfg(feature = "stubs")] +pub mod stubs; pub mod trade; use nautilus_core::time::UnixNanos; diff --git a/nautilus_core/model/src/data/quote.rs b/nautilus_core/model/src/data/quote.rs index c1aed9c6ec8d..2f72d088b5e8 100644 --- a/nautilus_core/model/src/data/quote.rs +++ b/nautilus_core/model/src/data/quote.rs @@ -18,16 +18,12 @@ use std::{ collections::HashMap, fmt::{Display, Formatter}, hash::Hash, - str::FromStr, }; use anyhow::Result; use indexmap::IndexMap; -use nautilus_core::{ - correctness::check_u8_equal, python::to_pyvalue_err, serialization::Serializable, - time::UnixNanos, -}; -use pyo3::prelude::*; +use nautilus_core::{correctness::check_u8_equal, serialization::Serializable, time::UnixNanos}; + use serde::{Deserialize, Serialize}; use crate::{ @@ -122,47 +118,6 @@ impl QuoteTick { metadata } - /// Create a new [`QuoteTick`] extracted from the given [`PyAny`]. - pub fn from_pyobject(obj: &PyAny) -> PyResult { - let instrument_id_obj: &PyAny = obj.getattr("instrument_id")?.extract()?; - let instrument_id_str = instrument_id_obj.getattr("value")?.extract()?; - let instrument_id = InstrumentId::from_str(instrument_id_str).map_err(to_pyvalue_err)?; - - let bid_price_py: &PyAny = obj.getattr("bid_price")?; - 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 ask_price_py: &PyAny = obj.getattr("ask_price")?; - 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 bid_size_py: &PyAny = obj.getattr("bid_size")?; - 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 ask_size_py: &PyAny = obj.getattr("ask_size")?; - 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 ts_event: UnixNanos = obj.getattr("ts_event")?.extract()?; - let ts_init: UnixNanos = obj.getattr("ts_init")?.extract()?; - - Self::new( - instrument_id, - bid_price, - ask_price, - bid_size, - ask_size, - ts_event, - ts_init, - ) - .map_err(to_pyvalue_err) - } - #[must_use] pub fn extract_price(&self, price_type: PriceType) -> Price { match price_type { diff --git a/nautilus_core/persistence/src/bin/drop_db.rs b/nautilus_core/model/src/data/stubs.rs similarity index 52% rename from nautilus_core/persistence/src/bin/drop_db.rs rename to nautilus_core/model/src/data/stubs.rs index f66d4d5551a1..c0f5696e19a5 100644 --- a/nautilus_core/persistence/src/bin/drop_db.rs +++ b/nautilus_core/model/src/data/stubs.rs @@ -13,22 +13,37 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- -use dotenv::dotenv; -use nautilus_persistence::db::database::{Database, DatabaseEngine}; +use rstest::fixture; -#[tokio::main] -async fn main() -> Result<(), Box> { - // load envs if exists - dotenv().ok(); +use super::*; +use crate::data::order::BookOrder; +use crate::enums::{BookAction, OrderSide}; +use crate::{ + identifiers::instrument_id::InstrumentId, + types::{price::Price, quantity::Quantity}, +}; - let db = Database::new(Some(DatabaseEngine::POSTGRES), None).await; +#[fixture] +pub fn stub_delta() -> OrderBookDelta { + let instrument_id = InstrumentId::from("AAPL.XNAS"); + let action = BookAction::Add; + let price = Price::from("100.00"); + let size = Quantity::from("10"); + let side = OrderSide::Buy; + let order_id = 123456; + let flags = 0; + let sequence = 1; + let ts_event = 1; + let ts_init = 2; - db.execute("DROP SCHEMA IF EXISTS nautilus CASCADE;") - .await - .map_err(|e| e.to_string())?; - db.execute("DROP ROLE IF EXISTS nautilus;") - .await - .map_err(|e| e.to_string())?; - println!("Dropped nautilus schema and role."); - Ok(()) + let order = BookOrder::new(side, price, size, order_id); + OrderBookDelta::new( + instrument_id, + action, + order, + flags, + sequence, + ts_event, + ts_init, + ) } diff --git a/nautilus_core/model/src/data/trade.rs b/nautilus_core/model/src/data/trade.rs index baf9575343db..b9c6a3e8a0e9 100644 --- a/nautilus_core/model/src/data/trade.rs +++ b/nautilus_core/model/src/data/trade.rs @@ -17,16 +17,14 @@ use std::{ collections::HashMap, fmt::{Display, Formatter}, hash::Hash, - str::FromStr, }; use indexmap::IndexMap; -use nautilus_core::{python::to_pyvalue_err, serialization::Serializable, time::UnixNanos}; -use pyo3::prelude::*; +use nautilus_core::{serialization::Serializable, time::UnixNanos}; use serde::{Deserialize, Serialize}; use crate::{ - enums::{AggressorSide, FromU8}, + enums::AggressorSide, identifiers::{instrument_id::InstrumentId, trade_id::TradeId}, types::{price::Price, quantity::Quantity}, }; @@ -105,44 +103,6 @@ impl TradeTick { metadata.insert("ts_init".to_string(), "UInt64".to_string()); metadata } - - /// Create a new [`TradeTick`] extracted from the given [`PyAny`]. - pub fn from_pyobject(obj: &PyAny) -> PyResult { - let instrument_id_obj: &PyAny = obj.getattr("instrument_id")?.extract()?; - let instrument_id_str = instrument_id_obj.getattr("value")?.extract()?; - let instrument_id = InstrumentId::from_str(instrument_id_str).map_err(to_pyvalue_err)?; - - let price_py: &PyAny = obj.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 size_py: &PyAny = obj.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 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 ts_event: UnixNanos = obj.getattr("ts_event")?.extract()?; - let ts_init: UnixNanos = obj.getattr("ts_init")?.extract()?; - - Ok(Self::new( - instrument_id, - price, - size, - aggressor_side, - trade_id, - ts_event, - ts_init, - )) - } } impl Display for TradeTick { diff --git a/nautilus_core/model/src/enums.rs b/nautilus_core/model/src/enums.rs index 9ce645667f2e..b7fb6383fba5 100644 --- a/nautilus_core/model/src/enums.rs +++ b/nautilus_core/model/src/enums.rs @@ -17,11 +17,10 @@ use std::str::FromStr; -use pyo3::{exceptions::PyValueError, prelude::*, types::PyType, PyTypeInfo}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use strum::{AsRefStr, Display, EnumIter, EnumString, FromRepr}; -use crate::{enum_for_python, enum_strum_serde, python::common::EnumIterator}; +use crate::enum_strum_serde; pub trait FromU8 { fn from_u8(value: u8) -> Option @@ -54,13 +53,10 @@ pub trait FromU8 { )] pub enum AccountType { /// An account with unleveraged cash assets only. - #[pyo3(name = "CASH")] Cash = 1, /// An account which facilitates trading on margin, using account assets as collateral. - #[pyo3(name = "MARGIN")] Margin = 2, /// An account specific to betting markets. - #[pyo3(name = "BETTING")] Betting = 3, } @@ -89,10 +85,8 @@ pub enum AccountType { )] pub enum AggregationSource { /// The data is externally aggregated (outside the Nautilus system boundary). - #[pyo3(name = "EXTERNAL")] External = 1, /// The data is internally aggregated (inside the Nautilus system boundary). - #[pyo3(name = "INTERNAL")] Internal = 2, } @@ -123,10 +117,8 @@ pub enum AggressorSide { /// There was no specific aggressor for the trade. NoAggressor = 0, /// The BUY order was the aggressor for the trade. - #[pyo3(name = "BUYER")] Buyer = 1, /// The SELL order was the aggressor for the trade. - #[pyo3(name = "SELLER")] Seller = 2, } @@ -167,25 +159,18 @@ impl FromU8 for AggressorSide { #[allow(non_camel_case_types)] pub enum AssetClass { /// Foreign exchange (FOREX) assets. - #[pyo3(name = "FX")] FX = 1, /// Equity / stock assets. - #[pyo3(name = "EQUITY")] Equity = 2, /// Commodity assets. - #[pyo3(name = "COMMODITY")] Commodity = 3, /// Debt based assets. - #[pyo3(name = "DEBT")] Debt = 4, /// Index based assets (baskets). - #[pyo3(name = "INDEX")] Index = 5, /// Cryptocurrency or crypto token assets. - #[pyo3(name = "CRYPTOCURRENCY")] Cryptocurrency = 6, /// Alternative assets. - #[pyo3(name = "ALTERNATIVE")] Alternative = 7, } @@ -214,37 +199,26 @@ pub enum AssetClass { )] pub enum InstrumentClass { /// A spot market instrument class. The current market price of an instrument that is bought or sold for immediate delivery and payment. - #[pyo3(name = "SPOT")] Spot = 1, /// A swap instrument class. A derivative contract through which two parties exchange the cash flows or liabilities from two different financial instruments. - #[pyo3(name = "SWAP")] Swap = 2, /// A futures contract instrument class. A legal agreement to buy or sell an asset at a predetermined price at a specified time in the future. - #[pyo3(name = "FUTURE")] Future = 3, /// A futures spread instrument class. A strategy involving the use of futures contracts to take advantage of price differentials between different contract months, underlying assets, or marketplaces. - #[pyo3(name = "FUTURE_SPREAD")] FutureSpread = 4, /// A forward derivative instrument class. A customized contract between two parties to buy or sell an asset at a specified price on a future date. - #[pyo3(name = "FORWARD")] Forward = 5, /// A contract-for-difference (CFD) instrument class. A contract between an investor and a CFD broker to exchange the difference in the value of a financial product between the time the contract opens and closes. - #[pyo3(name = "CFD")] Cfd = 6, /// A bond instrument class. A type of debt investment where an investor loans money to an entity (typically corporate or governmental) which borrows the funds for a defined period of time at a variable or fixed interest rate. - #[pyo3(name = "BOND")] Bond = 7, /// An options contract instrument class. A type of derivative that gives the holder the right, but not the obligation, to buy or sell an underlying asset at a predetermined price before or at a certain future date. - #[pyo3(name = "OPTION")] Option = 8, /// An option spread instrument class. A strategy involving the purchase and/or sale of options on the same underlying asset with different strike prices or expiration dates to capitalize on expected market moves in a controlled cost environment. - #[pyo3(name = "OPTION_SPREAD")] OptionSpread = 9, /// A warrant instrument class. A derivative that gives the holder the right, but not the obligation, to buy or sell a security—most commonly an equity—at a certain price before expiration. - #[pyo3(name = "WARRANT")] Warrant = 10, /// A warrant instrument class. A derivative that gives the holder the right, but not the obligation, to buy or sell a security—most commonly an equity—at a certain price before expiration. - #[pyo3(name = "SPORTS_BETTING")] SportsBetting = 11, } @@ -273,52 +247,36 @@ pub enum InstrumentClass { )] pub enum BarAggregation { /// Based on a number of ticks. - #[pyo3(name = "TICK")] Tick = 1, /// Based on the buy/sell imbalance of ticks. - #[pyo3(name = "TICK_IMBALANCE")] TickImbalance = 2, /// Based on sequential buy/sell runs of ticks. - #[pyo3(name = "TICK_RUNS")] TickRuns = 3, /// Based on trading volume. - #[pyo3(name = "VOLUME")] Volume = 4, /// Based on the buy/sell imbalance of trading volume. - #[pyo3(name = "VOLUME_IMBALANCE")] VolumeImbalance = 5, /// Based on sequential runs of buy/sell trading volume. - #[pyo3(name = "VOLUME_RUNS")] VolumeRuns = 6, /// Based on the 'notional' value of the instrument. - #[pyo3(name = "VALUE")] Value = 7, /// Based on the buy/sell imbalance of trading by 'notional' value. - #[pyo3(name = "VALUE_IMBALANCE")] ValueImbalance = 8, /// Based on sequential buy/sell runs of trading by 'notional' value. - #[pyo3(name = "VALUE_RUNS")] ValueRuns = 9, /// Based on time intervals with millisecond granularity. - #[pyo3(name = "MILLISECOND")] Millisecond = 10, /// Based on time intervals with second granularity. - #[pyo3(name = "SECOND")] Second = 11, /// Based on time intervals with minute granularity. - #[pyo3(name = "MINUTE")] Minute = 12, /// Based on time intervals with hour granularity. - #[pyo3(name = "HOUR")] Hour = 13, /// Based on time intervals with day granularity. - #[pyo3(name = "DAY")] Day = 14, /// Based on time intervals with week granularity. - #[pyo3(name = "WEEK")] Week = 15, /// Based on time intervals with month granularity. - #[pyo3(name = "MONTH")] Month = 16, } @@ -347,16 +305,12 @@ pub enum BarAggregation { )] pub enum BookAction { /// An order is added to the book. - #[pyo3(name = "ADD")] Add = 1, /// An existing order in the book is updated/modified. - #[pyo3(name = "UPDATE")] Update = 2, /// An existing order in the book is deleted/canceled. - #[pyo3(name = "DELETE")] Delete = 3, /// The state of the order book is cleared. - #[pyo3(name = "CLEAR")] Clear = 4, } @@ -445,13 +399,10 @@ pub enum ContingencyType { /// Not a contingent order. NoContingency = 0, // Will be replaced by `Option` /// One-Cancels-the-Other. - #[pyo3(name = "OCO")] Oco = 1, /// One-Triggers-the-Other. - #[pyo3(name = "OTO")] Oto = 2, /// One-Updates-the-Other (by proportional quantity). - #[pyo3(name = "OUO")] Ouo = 3, } @@ -480,13 +431,10 @@ pub enum ContingencyType { )] pub enum CurrencyType { /// A type of cryptocurrency or crypto token. - #[pyo3(name = "CRYPTO")] Crypto = 1, /// A type of currency issued by governments which is not backed by a commodity. - #[pyo3(name = "FIAT")] Fiat = 2, /// A type of currency that is based on the value of an underlying commodity. - #[pyo3(name = "COMMODITY_BACKED")] CommodityBacked = 3, } @@ -515,10 +463,8 @@ pub enum CurrencyType { )] pub enum InstrumentCloseType { /// When the market session ended. - #[pyo3(name = "END_OF_SESSION")] EndOfSession = 1, /// When the instrument expiration was reached. - #[pyo3(name = "CONTRACT_EXPIRED")] ContractExpired = 2, } @@ -548,13 +494,10 @@ pub enum InstrumentCloseType { #[allow(clippy::enum_variant_names)] pub enum LiquiditySide { /// No specific liqudity side. - #[pyo3(name = "NO_LIQUIDITY_SIDE")] NoLiquiditySide = 0, // Will be replaced by `Option` /// The order passively provided liqudity to the market to complete the trade (made a market). - #[pyo3(name = "MAKER")] Maker = 1, /// The order aggressively took liqudity from the market to complete the trade. - #[pyo3(name = "TAKER")] Taker = 2, } @@ -583,25 +526,18 @@ pub enum LiquiditySide { )] pub enum MarketStatus { /// The market session is in the pre-open. - #[pyo3(name = "PRE_OPEN")] PreOpen = 1, /// The market session is open. - #[pyo3(name = "OPEN")] Open = 2, /// The market session is paused. - #[pyo3(name = "PAUSE")] Pause = 3, /// The market session is halted. - #[pyo3(name = "HALT")] Halt = 4, /// The market session has reopened after a pause or halt. - #[pyo3(name = "REOPEN")] Reopen = 5, /// The market session is in the pre-close. - #[pyo3(name = "PRE_CLOSE")] PreClose = 6, /// The market session is closed. - #[pyo3(name = "CLOSED")] Closed = 7, } @@ -630,13 +566,10 @@ pub enum MarketStatus { )] pub enum HaltReason { /// The venue or market session is not halted. - #[pyo3(name = "NOT_HALTED")] NotHalted = 1, /// Trading halt is imposed for purely regulatory reasons with/without volatility halt. - #[pyo3(name = "GENERAL")] General = 2, /// Trading halt is imposed by the venue to protect against extreme volatility. - #[pyo3(name = "VOLATILITY")] Volatility = 3, } @@ -667,12 +600,10 @@ pub enum OmsType { /// There is no specific type of order management specified (will defer to the venue). Unspecified = 0, // Will be replaced by `Option` /// The netting type where there is one position per instrument. - #[pyo3(name = "NETTING")] Netting = 1, /// The hedging type where there can be multiple positions per instrument. /// This can be in LONG/SHORT directions, by position/ticket ID, or tracked virtually by /// Nautilus. - #[pyo3(name = "HEDGING")] Hedging = 2, } @@ -701,10 +632,8 @@ pub enum OmsType { )] pub enum OptionKind { /// A Call option gives the holder the right, but not the obligation, to buy an underlying asset at a specified strike price within a specified period of time. - #[pyo3(name = "CALL")] Call = 1, /// A Put option gives the holder the right, but not the obligation, to sell an underlying asset at a specified strike price within a specified period of time. - #[pyo3(name = "PUT")] Put = 2, } @@ -736,10 +665,8 @@ pub enum OrderSide { /// No order side is specified. NoOrderSide = 0, /// The order is a BUY. - #[pyo3(name = "BUY")] Buy = 1, /// The order is a SELL. - #[pyo3(name = "SELL")] Sell = 2, } @@ -799,46 +726,32 @@ impl FromU8 for OrderSide { )] pub enum OrderStatus { /// The order is initialized (instantiated) within the Nautilus system. - #[pyo3(name = "INITIALIZED")] Initialized = 1, /// The order was denied by the Nautilus system, either for being invalid, unprocessable or exceeding a risk limit. - #[pyo3(name = "DENIED")] Denied = 2, /// The order became emulated by the Nautilus system in the `OrderEmulator` component. - #[pyo3(name = "EMULATED")] Emulated = 3, /// The order was released by the Nautilus system from the `OrderEmulator` component. - #[pyo3(name = "RELEASED")] Released = 4, /// The order was submitted by the Nautilus system to the external service or trading venue (awaiting acknowledgement). - #[pyo3(name = "SUBMITTED")] Submitted = 5, /// The order was acknowledged by the trading venue as being received and valid (may now be working). - #[pyo3(name = "ACCEPTED")] Accepted = 6, /// The order was rejected by the trading venue. - #[pyo3(name = "REJECTED")] Rejected = 7, /// The order was canceled (closed/done). - #[pyo3(name = "CANCELED")] Canceled = 8, /// The order reached a GTD expiration (closed/done). - #[pyo3(name = "EXPIRED")] Expired = 9, /// The order STOP price was triggered on a trading venue. - #[pyo3(name = "TRIGGERED")] Triggered = 10, /// The order is currently pending a request to modify on a trading venue. - #[pyo3(name = "PENDING_UPDATE")] PendingUpdate = 11, /// The order is currently pending a request to cancel on a trading venue. - #[pyo3(name = "PENDING_CANCEL")] PendingCancel = 12, /// The order has been partially filled on a trading venue. - #[pyo3(name = "PARTIALLY_FILLED")] PartiallyFilled = 13, /// The order has been completely filled on a trading venue (closed/done). - #[pyo3(name = "FILLED")] Filled = 14, } @@ -867,31 +780,22 @@ pub enum OrderStatus { )] pub enum OrderType { /// A market order to buy or sell at the best available price in the current market. - #[pyo3(name = "MARKET")] Market = 1, /// A limit order to buy or sell at a specific price or better. - #[pyo3(name = "LIMIT")] Limit = 2, /// A stop market order to buy or sell once the price reaches the specified stop/trigger price. When the stop price is reached, the order effectively becomes a market order. - #[pyo3(name = "STOP_MARKET")] StopMarket = 3, /// A stop limit order to buy or sell which combines the features of a stop order and a limit order. Once the stop/trigger price is reached, a stop-limit order effectively becomes a limit order. - #[pyo3(name = "STOP_LIMIT")] StopLimit = 4, /// A market-to-limit order is a market order that is to be executed as a limit order at the current best market price after reaching the market. - #[pyo3(name = "MARKET_TO_LIMIT")] MarketToLimit = 5, /// A market-if-touched order effectively becomes a market order when the specified trigger price is reached. - #[pyo3(name = "MARKET_IF_TOUCHED")] MarketIfTouched = 6, /// A limit-if-touched order effectively becomes a limit order when the specified trigger price is reached. - #[pyo3(name = "LIMIT_IF_TOUCHED")] LimitIfTouched = 7, /// A trailing stop market order sets the stop/trigger price at a fixed "trailing offset" amount from the market. - #[pyo3(name = "TRAILING_STOP_MARKET")] TrailingStopMarket = 8, /// A trailing stop limit order combines the features of a trailing stop order with those of a limit order. - #[pyo3(name = "TRAILING_STOP_LIMIT")] TrailingStopLimit = 9, } @@ -923,13 +827,10 @@ pub enum PositionSide { /// No position side is specified (only valid in the context of a filter for actions involving positions). NoPositionSide = 0, // Will be replaced by `Option` /// A neural/flat position, where no position is currently held in the market. - #[pyo3(name = "FLAT")] Flat = 1, /// A long position in the market, typically acquired through one or many BUY orders. - #[pyo3(name = "LONG")] Long = 2, /// A short position in the market, typically acquired through one or many SELL orders. - #[pyo3(name = "SHORT")] Short = 3, } @@ -958,16 +859,12 @@ pub enum PositionSide { )] pub enum PriceType { /// A quoted order price where a buyer is willing to buy a quantity of an instrument. - #[pyo3(name = "BID")] Bid = 1, /// A quoted order price where a seller is willing to sell a quantity of an instrument. - #[pyo3(name = "ASK")] Ask = 2, /// The midpoint between the bid and ask prices. - #[pyo3(name = "MID")] Mid = 3, /// The last price at which a trade was made for an instrument. - #[pyo3(name = "LAST")] Last = 4, } @@ -996,25 +893,18 @@ pub enum PriceType { )] pub enum TimeInForce { /// Good Till Canceled (GTC) - the order remains active until canceled. - #[pyo3(name = "GTC")] Gtc = 1, /// Immediate or Cancel (IOC) - the order is filled as much as possible, the rest is canceled. - #[pyo3(name = "IOC")] Ioc = 2, /// Fill or Kill (FOK) - the order must be executed in full immediately, or it is canceled. - #[pyo3(name = "FOK")] Fok = 3, /// Good Till Date/Time (GTD) - the order is active until a specified date or time. - #[pyo3(name = "GTD")] Gtd = 4, /// Day - the order is active until the end of the current trading session. - #[pyo3(name = "DAY")] Day = 5, /// At the Opening (ATO) - the order is scheduled to be executed at the market's opening. - #[pyo3(name = "AT_THE_OPEN")] AtTheOpen = 6, /// At the Closing (ATC) - the order is scheduled to be executed at the market's closing. - #[pyo3(name = "AT_THE_CLOSE")] AtTheClose = 7, } @@ -1043,13 +933,10 @@ pub enum TimeInForce { )] pub enum TradingState { /// Normal trading operations. - #[pyo3(name = "ACTIVE")] Active = 1, /// Trading is completely halted, no new order commands will be emitted. - #[pyo3(name = "HALTED")] Halted = 2, /// Only order commands which would cancel order, or reduce position sizes are permitted. - #[pyo3(name = "REDUCING")] Reducing = 3, } @@ -1080,16 +967,12 @@ pub enum TrailingOffsetType { /// No trailing offset type is specified (invalid for trailing type orders). NoTrailingOffset = 0, // Will be replaced by `Option` /// The trailing offset is based on a market price. - #[pyo3(name = "PRICE")] Price = 1, /// The trailing offset is based on a percentage represented in basis points, of a market price. - #[pyo3(name = "BASIS_POINTS")] BasisPoints = 2, /// The trailing offset is based on the number of ticks from a market price. - #[pyo3(name = "TICKS")] Ticks = 3, /// The trailing offset is based on a price tier set by a specific trading venue. - #[pyo3(name = "PRICE_TIER")] PriceTier = 4, } @@ -1120,31 +1003,22 @@ pub enum TriggerType { /// No trigger type is specified (invalid for orders with a trigger). NoTrigger = 0, // Will be replaced by `Option` /// The default trigger type set by the trading venue. - #[pyo3(name = "DEFAULT")] Default = 1, /// Based on the top-of-book quoted prices for the instrument. - #[pyo3(name = "BID_ASK")] BidAsk = 2, /// Based on the last traded price for the instrument. - #[pyo3(name = "LAST_TRADE")] LastTrade = 3, /// Based on a 'double match' of the last traded price for the instrument - #[pyo3(name = "DOUBLE_LAST")] DoubleLast = 4, /// Based on a 'double match' of the bid/ask price for the instrument - #[pyo3(name = "DOUBLE_BID_ASK")] DoubleBidAsk = 5, /// Based on both the [`TriggerType::LastTrade`] and [`TriggerType::BidAsk`]. - #[pyo3(name = "LAST_OR_BID_ASK")] LastOrBidAsk = 6, /// Based on the mid-point of the [`TriggerType::BidAsk`]. - #[pyo3(name = "MID_POINT")] MidPoint = 7, /// Based on the mark price for the instrument. - #[pyo3(name = "MARK_PRICE")] MarkPrice = 8, /// Based on the index price for the instrument. - #[pyo3(name = "INDEX_PRICE")] IndexPrice = 9, } @@ -1172,27 +1046,3 @@ enum_strum_serde!(TimeInForce); enum_strum_serde!(TradingState); enum_strum_serde!(TrailingOffsetType); enum_strum_serde!(TriggerType); - -enum_for_python!(AccountType); -enum_for_python!(AggregationSource); -enum_for_python!(AggressorSide); -enum_for_python!(AssetClass); -enum_for_python!(BarAggregation); -enum_for_python!(BookAction); -enum_for_python!(BookType); -enum_for_python!(ContingencyType); -enum_for_python!(CurrencyType); -enum_for_python!(InstrumentCloseType); -enum_for_python!(LiquiditySide); -enum_for_python!(MarketStatus); -enum_for_python!(OmsType); -enum_for_python!(OptionKind); -enum_for_python!(OrderSide); -enum_for_python!(OrderStatus); -enum_for_python!(OrderType); -enum_for_python!(PositionSide); -enum_for_python!(PriceType); -enum_for_python!(TimeInForce); -enum_for_python!(TradingState); -enum_for_python!(TrailingOffsetType); -enum_for_python!(TriggerType); diff --git a/nautilus_core/model/src/events/order/filled.rs b/nautilus_core/model/src/events/order/filled.rs index 3bcb31ba0d4c..19f6aee315cb 100644 --- a/nautilus_core/model/src/events/order/filled.rs +++ b/nautilus_core/model/src/events/order/filled.rs @@ -39,43 +39,24 @@ use crate::{ pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model") )] pub struct OrderFilled { - #[pyo3(get)] pub trader_id: TraderId, - #[pyo3(get)] pub strategy_id: StrategyId, - #[pyo3(get)] pub instrument_id: InstrumentId, - #[pyo3(get)] pub client_order_id: ClientOrderId, - #[pyo3(get)] pub venue_order_id: VenueOrderId, - #[pyo3(get)] pub account_id: AccountId, - #[pyo3(get)] pub trade_id: TradeId, - #[pyo3(get)] pub order_side: OrderSide, - #[pyo3(get)] pub order_type: OrderType, - #[pyo3(get)] pub last_qty: Quantity, - #[pyo3(get)] pub last_px: Price, - #[pyo3(get)] pub currency: Currency, - #[pyo3(get)] pub liquidity_side: LiquiditySide, - #[pyo3(get)] pub event_id: UUID4, - #[pyo3(get)] pub ts_event: UnixNanos, - #[pyo3(get)] pub ts_init: UnixNanos, - #[pyo3(get)] pub reconciliation: bool, - #[pyo3(get)] pub position_id: Option, - #[pyo3(get)] pub commission: Option, } diff --git a/nautilus_core/model/src/events/order/rejected.rs b/nautilus_core/model/src/events/order/rejected.rs index 146e804a52be..4efdca631884 100644 --- a/nautilus_core/model/src/events/order/rejected.rs +++ b/nautilus_core/model/src/events/order/rejected.rs @@ -18,6 +18,7 @@ use std::fmt::{Display, Formatter}; use anyhow::Result; use derive_builder::Builder; use nautilus_core::{time::UnixNanos, uuid::UUID4}; + use serde::{Deserialize, Serialize}; use ustr::Ustr; diff --git a/nautilus_core/model/src/events/order/released.rs b/nautilus_core/model/src/events/order/released.rs index d35d9169088d..2fd459cc6f4c 100644 --- a/nautilus_core/model/src/events/order/released.rs +++ b/nautilus_core/model/src/events/order/released.rs @@ -18,6 +18,7 @@ use std::fmt::Display; use anyhow::Result; use derive_builder::Builder; use nautilus_core::{time::UnixNanos, uuid::UUID4}; + use serde::{Deserialize, Serialize}; use crate::{ diff --git a/nautilus_core/model/src/events/order/triggered.rs b/nautilus_core/model/src/events/order/triggered.rs index 26e3e1bd6ac4..03f69d755df9 100644 --- a/nautilus_core/model/src/events/order/triggered.rs +++ b/nautilus_core/model/src/events/order/triggered.rs @@ -18,6 +18,7 @@ use std::fmt::Display; use anyhow::Result; use derive_builder::Builder; use nautilus_core::{time::UnixNanos, uuid::UUID4}; + use serde::{Deserialize, Serialize}; use crate::identifiers::{ diff --git a/nautilus_core/model/src/identifiers/mod.rs b/nautilus_core/model/src/identifiers/mod.rs index b3b2050508dc..d952191a2f75 100644 --- a/nautilus_core/model/src/identifiers/mod.rs +++ b/nautilus_core/model/src/identifiers/mod.rs @@ -15,16 +15,7 @@ use std::str::FromStr; -use nautilus_core::python::to_pyvalue_err; -use pyo3::{ - prelude::*, - pyclass::CompareOp, - types::{PyString, PyTuple}, -}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use ustr::Ustr; - -use crate::identifier_for_python; #[macro_use] mod macros; @@ -74,19 +65,6 @@ impl_serialization_for_identifier!(trader_id::TraderId); impl_serialization_for_identifier!(venue::Venue); impl_serialization_for_identifier!(venue_order_id::VenueOrderId); -identifier_for_python!(account_id::AccountId); -identifier_for_python!(client_id::ClientId); -identifier_for_python!(client_order_id::ClientOrderId); -identifier_for_python!(component_id::ComponentId); -identifier_for_python!(exec_algorithm_id::ExecAlgorithmId); -identifier_for_python!(order_list_id::OrderListId); -identifier_for_python!(position_id::PositionId); -identifier_for_python!(strategy_id::StrategyId); -identifier_for_python!(symbol::Symbol); -identifier_for_python!(trader_id::TraderId); -identifier_for_python!(venue::Venue); -identifier_for_python!(venue_order_id::VenueOrderId); - #[no_mangle] pub extern "C" fn interned_string_stats() { dbg!(ustr::total_allocated()); diff --git a/nautilus_core/model/src/instruments/synthetic.rs b/nautilus_core/model/src/instruments/synthetic.rs index d817472df32c..75fbd9c2e636 100644 --- a/nautilus_core/model/src/instruments/synthetic.rs +++ b/nautilus_core/model/src/instruments/synthetic.rs @@ -35,19 +35,12 @@ use crate::{ pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model") )] pub struct SyntheticInstrument { - #[pyo3(get)] pub id: InstrumentId, - #[pyo3(get)] pub price_precision: u8, - #[pyo3(get)] pub price_increment: Price, - #[pyo3(get)] pub components: Vec, - #[pyo3(get)] pub formula: String, - #[pyo3(get)] pub ts_event: UnixNanos, - #[pyo3(get)] pub ts_init: UnixNanos, context: HashMapContext, variables: Vec, diff --git a/nautilus_core/model/src/position.rs b/nautilus_core/model/src/position.rs index ca9c75f664d2..722b89c71356 100644 --- a/nautilus_core/model/src/position.rs +++ b/nautilus_core/model/src/position.rs @@ -43,7 +43,7 @@ use crate::{ #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr( feature = "python", - pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.common") + pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model") )] pub struct Position { pub events: Vec, diff --git a/nautilus_core/model/src/python/data/delta.rs b/nautilus_core/model/src/python/data/delta.rs index 6f3e42133ff4..e74ece946c0c 100644 --- a/nautilus_core/model/src/python/data/delta.rs +++ b/nautilus_core/model/src/python/data/delta.rs @@ -13,6 +13,8 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- +use pyo3::basic::CompareOp; +use std::str::FromStr; use std::{ collections::{hash_map::DefaultHasher, HashMap}, hash::{Hash, Hasher}, @@ -23,9 +25,14 @@ use nautilus_core::{ serialization::Serializable, time::UnixNanos, }; -use pyo3::{prelude::*, pyclass::CompareOp, types::PyDict}; +use pyo3::prelude::*; +use pyo3::types::PyDict; use super::data_to_pycapsule; +use crate::data::order::{OrderId, NULL_ORDER}; +use crate::enums::{FromU8, OrderSide}; +use crate::types::price::Price; +use crate::types::quantity::Quantity; use crate::{ data::{delta::OrderBookDelta, order::BookOrder, Data}, enums::BookAction, @@ -33,6 +40,63 @@ use crate::{ python::common::PY_MODULE_MODEL, }; +impl OrderBookDelta { + /// Create a new [`OrderBookDelta`] extracted from the given [`PyAny`]. + pub fn from_pyobject(obj: &PyAny) -> PyResult { + let instrument_id_obj: &PyAny = obj.getattr("instrument_id")?.extract()?; + let instrument_id_str = instrument_id_obj.getattr("value")?.extract()?; + let instrument_id = InstrumentId::from_str(instrument_id_str) + .map_err(to_pyvalue_err) + .unwrap(); + + let action_obj: &PyAny = obj.getattr("action")?.extract()?; + let action_u8 = action_obj.getattr("value")?.extract()?; + let action = BookAction::from_u8(action_u8).unwrap(); + + let flags: u8 = obj.getattr("flags")?.extract()?; + let sequence: u64 = obj.getattr("sequence")?.extract()?; + let ts_event: UnixNanos = obj.getattr("ts_event")?.extract()?; + let ts_init: UnixNanos = obj.getattr("ts_init")?.extract()?; + + let order_pyobject = obj.getattr("order")?; + let order: BookOrder = if order_pyobject.is_none() { + NULL_ORDER + } else { + let side_obj: &PyAny = order_pyobject.getattr("side")?.extract()?; + let side_u8 = side_obj.getattr("value")?.extract()?; + let side = OrderSide::from_u8(side_u8).unwrap(); + + 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 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 order_id: OrderId = order_pyobject.getattr("order_id")?.extract()?; + BookOrder { + side, + price, + size, + order_id, + } + }; + + Ok(Self::new( + instrument_id, + action, + order, + flags, + sequence, + ts_event, + ts_init, + )) + } +} + #[pymethods] impl OrderBookDelta { #[new] @@ -226,7 +290,7 @@ mod tests { use rstest::rstest; use super::*; - use crate::data::delta::stubs::stub_delta; + use crate::data::stubs::*; #[rstest] fn test_as_dict(stub_delta: OrderBookDelta) { diff --git a/nautilus_core/model/src/python/data/depth.rs b/nautilus_core/model/src/python/data/depth.rs index c5d75e050df3..b50f15ce73c9 100644 --- a/nautilus_core/model/src/python/data/depth.rs +++ b/nautilus_core/model/src/python/data/depth.rs @@ -26,6 +26,7 @@ use nautilus_core::{ use pyo3::{prelude::*, pyclass::CompareOp, types::PyDict}; use super::data_to_pycapsule; +use crate::python::common::PY_MODULE_MODEL; use crate::{ data::{ depth::{OrderBookDepth10, DEPTH10_LEN}, @@ -34,7 +35,6 @@ use crate::{ }, enums::OrderSide, identifiers::instrument_id::InstrumentId, - python::common::PY_MODULE_MODEL, types::{price::Price, quantity::Quantity}, }; diff --git a/nautilus_core/model/src/python/data/mod.rs b/nautilus_core/model/src/python/data/mod.rs index 9916e5896b75..0bf72ad5a6c0 100644 --- a/nautilus_core/model/src/python/data/mod.rs +++ b/nautilus_core/model/src/python/data/mod.rs @@ -21,11 +21,13 @@ pub mod order; pub mod quote; pub mod trade; -use nautilus_core::ffi::cvec::CVec; use pyo3::{prelude::*, types::PyCapsule}; use crate::data::Data; +#[cfg(feature = "ffi")] +use nautilus_core::ffi::cvec::CVec; + /// Creates a Python `PyCapsule` object containing a Rust `Data` instance. /// /// This function takes ownership of the `Data` instance and encapsulates it within @@ -67,6 +69,7 @@ pub fn data_to_pycapsule(py: Python, data: Data) -> PyObject { /// management. The caller must ensure the `PyCapsule` contains a valid `CVec` pointer. /// Incorrect usage can lead to memory corruption or undefined behavior. #[pyfunction] +#[cfg(feature = "ffi")] pub fn drop_cvec_pycapsule(capsule: &PyAny) { let capsule: &PyCapsule = capsule .downcast() @@ -76,3 +79,9 @@ pub fn drop_cvec_pycapsule(capsule: &PyAny) { unsafe { Vec::from_raw_parts(cvec.ptr.cast::(), cvec.len, cvec.cap) }; drop(data); } + +#[pyfunction] +#[cfg(not(feature = "ffi"))] +pub fn drop_cvec_pycapsule(_capsule: &PyAny) { + panic!("`ffi` feature is not enabled"); +} diff --git a/nautilus_core/model/src/python/data/quote.rs b/nautilus_core/model/src/python/data/quote.rs index a3dd4e1f4079..a7f99c19f9cb 100644 --- a/nautilus_core/model/src/python/data/quote.rs +++ b/nautilus_core/model/src/python/data/quote.rs @@ -39,6 +39,49 @@ use crate::{ types::{price::Price, quantity::Quantity}, }; +impl QuoteTick { + /// Create a new [`QuoteTick`] extracted from the given [`PyAny`]. + pub fn from_pyobject(obj: &PyAny) -> PyResult { + let instrument_id_obj: &PyAny = obj.getattr("instrument_id")?.extract()?; + let instrument_id_str = instrument_id_obj.getattr("value")?.extract()?; + let instrument_id = InstrumentId::from_str(instrument_id_str).map_err(to_pyvalue_err)?; + + let bid_price_py: &PyAny = obj.getattr("bid_price")?; + 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 ask_price_py: &PyAny = obj.getattr("ask_price")?; + 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 bid_size_py: &PyAny = obj.getattr("bid_size")?; + 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 ask_size_py: &PyAny = obj.getattr("ask_size")?; + 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 ts_event: UnixNanos = obj.getattr("ts_event")?.extract()?; + let ts_init: UnixNanos = obj.getattr("ts_init")?.extract()?; + + Self::new( + instrument_id, + bid_price, + ask_price, + bid_size, + ask_size, + ts_event, + ts_init, + ) + .map_err(to_pyvalue_err) + } +} + #[pymethods] impl QuoteTick { #[new] diff --git a/nautilus_core/model/src/python/data/trade.rs b/nautilus_core/model/src/python/data/trade.rs index b9c4c32f4325..bb6e3bfb8494 100644 --- a/nautilus_core/model/src/python/data/trade.rs +++ b/nautilus_core/model/src/python/data/trade.rs @@ -39,6 +39,46 @@ use crate::{ types::{price::Price, quantity::Quantity}, }; +impl TradeTick { + /// Create a new [`TradeTick`] extracted from the given [`PyAny`]. + pub fn from_pyobject(obj: &PyAny) -> PyResult { + let instrument_id_obj: &PyAny = obj.getattr("instrument_id")?.extract()?; + let instrument_id_str = instrument_id_obj.getattr("value")?.extract()?; + let instrument_id = InstrumentId::from_str(instrument_id_str).map_err(to_pyvalue_err)?; + + let price_py: &PyAny = obj.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 size_py: &PyAny = obj.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 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 ts_event: UnixNanos = obj.getattr("ts_event")?.extract()?; + let ts_init: UnixNanos = obj.getattr("ts_init")?.extract()?; + + Ok(Self::new( + instrument_id, + price, + size, + aggressor_side, + trade_id, + ts_event, + ts_init, + )) + } +} + #[pymethods] impl TradeTick { #[new] diff --git a/nautilus_core/model/src/python/enums.rs b/nautilus_core/model/src/python/enums.rs new file mode 100644 index 000000000000..6c819c36f933 --- /dev/null +++ b/nautilus_core/model/src/python/enums.rs @@ -0,0 +1,834 @@ +// ------------------------------------------------------------------------------------------------- +// 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 crate::enum_for_python; +use crate::enums::{ + AccountType, AggregationSource, AggressorSide, AssetClass, BarAggregation, BookAction, + BookType, ContingencyType, CurrencyType, HaltReason, InstrumentClass, InstrumentCloseType, + LiquiditySide, MarketStatus, OmsType, OptionKind, OrderSide, OrderStatus, OrderType, + PositionSide, PriceType, TimeInForce, TradingState, TrailingOffsetType, TriggerType, +}; +use crate::python::common::EnumIterator; +use pyo3::{exceptions::PyValueError, prelude::*, types::PyType, PyTypeInfo}; +use std::str::FromStr; + +#[pymethods] +impl AccountType { + #[classattr] + #[pyo3(name = "CASH")] + fn py_cash() -> Self { + AccountType::Cash + } + #[classattr] + #[pyo3(name = "MARGIN")] + fn py_margin() -> Self { + AccountType::Margin + } + #[classattr] + #[pyo3(name = "BETTING")] + fn py_betting() -> Self { + AccountType::Betting + } +} + +#[pymethods] +impl AggregationSource { + #[classattr] + #[pyo3(name = "EXTERNAL")] + fn py_external() -> Self { + AggregationSource::External + } + #[classattr] + #[pyo3(name = "INTERNAL")] + fn py_internal() -> Self { + AggregationSource::Internal + } +} + +#[pymethods] +impl AggressorSide { + #[classattr] + #[pyo3(name = "NO_AGGRESSOR")] + fn py_no_aggressor() -> Self { + AggressorSide::NoAggressor + } + + #[classattr] + #[pyo3(name = "BUYER")] + fn py_buyer() -> Self { + AggressorSide::Buyer + } + + #[classattr] + #[pyo3(name = "SELLER")] + fn py_seller() -> Self { + AggressorSide::Seller + } +} + +#[pymethods] +impl AssetClass { + #[classattr] + #[pyo3(name = "FX")] + fn py_fx() -> Self { + AssetClass::FX + } + #[classattr] + #[pyo3(name = "EQUITY")] + fn py_equity() -> Self { + AssetClass::Equity + } + #[classattr] + #[pyo3(name = "COMMODITY")] + fn py_commodity() -> Self { + AssetClass::Commodity + } + #[classattr] + #[pyo3(name = "DEBT")] + fn py_debt() -> Self { + AssetClass::Debt + } + #[classattr] + #[pyo3(name = "INDEX")] + fn py_index() -> Self { + AssetClass::Index + } + #[classattr] + #[pyo3(name = "CRYPTOCURRENCY")] + fn py_cryptocurrency() -> Self { + AssetClass::Cryptocurrency + } + #[classattr] + #[pyo3(name = "ALTERNATIVE")] + fn py_alternative() -> Self { + AssetClass::Alternative + } +} + +#[pymethods] +impl InstrumentClass { + #[classattr] + #[pyo3(name = "SPOT")] + fn py_spot() -> Self { + InstrumentClass::Spot + } + #[classattr] + #[pyo3(name = "SWAP")] + fn py_swap() -> Self { + InstrumentClass::Swap + } + #[classattr] + #[pyo3(name = "FUTURE")] + fn py_future() -> Self { + InstrumentClass::Future + } + #[classattr] + #[pyo3(name = "FORWARD")] + fn py_forward() -> Self { + InstrumentClass::Forward + } + #[classattr] + #[pyo3(name = "CFD")] + fn py_cfd() -> Self { + InstrumentClass::Cfd + } + #[classattr] + #[pyo3(name = "BOND")] + fn py_bond() -> Self { + InstrumentClass::Bond + } + #[classattr] + #[pyo3(name = "OPTION")] + fn py_option() -> Self { + InstrumentClass::Option + } + #[classattr] + #[pyo3(name = "WARRANT")] + fn py_warrant() -> Self { + InstrumentClass::Warrant + } + #[classattr] + #[pyo3(name = "SPORTS_BETTING")] + fn py_sports_betting() -> Self { + InstrumentClass::SportsBetting + } +} + +#[pymethods] +impl BarAggregation { + #[classattr] + #[pyo3(name = "TICK")] + fn py_tick() -> Self { + BarAggregation::Tick + } + + #[classattr] + #[pyo3(name = "TICK_IMBALANCE")] + fn py_tick_imbalance() -> Self { + BarAggregation::TickImbalance + } + + #[classattr] + #[pyo3(name = "TICK_RUNS")] + fn py_tick_runs() -> Self { + BarAggregation::TickRuns + } + + #[classattr] + #[pyo3(name = "VOLUME")] + fn py_volume() -> Self { + BarAggregation::Volume + } + + #[classattr] + #[pyo3(name = "VOLUME_IMBALANCE")] + fn py_volume_imbalance() -> Self { + BarAggregation::VolumeImbalance + } + + #[classattr] + #[pyo3(name = "VOLUME_RUNS")] + fn py_volume_runs() -> Self { + BarAggregation::VolumeRuns + } + + #[classattr] + #[pyo3(name = "VALUE")] + fn py_value() -> Self { + BarAggregation::Value + } + + #[classattr] + #[pyo3(name = "VALUE_IMBALANCE")] + fn py_value_imbalance() -> Self { + BarAggregation::ValueImbalance + } + + #[classattr] + #[pyo3(name = "VALUE_RUNS")] + fn py_value_runs() -> Self { + BarAggregation::ValueRuns + } + + #[classattr] + #[pyo3(name = "MILLISECOND")] + fn py_millisecond() -> Self { + BarAggregation::Millisecond + } + + #[classattr] + #[pyo3(name = "SECOND")] + fn py_second() -> Self { + BarAggregation::Second + } + + #[classattr] + #[pyo3(name = "MINUTE")] + fn py_minute() -> Self { + BarAggregation::Minute + } + + #[classattr] + #[pyo3(name = "HOUR")] + fn py_hour() -> Self { + BarAggregation::Hour + } + + #[classattr] + #[pyo3(name = "DAY")] + fn py_day() -> Self { + BarAggregation::Day + } + + #[classattr] + #[pyo3(name = "WEEK")] + fn py_week() -> Self { + BarAggregation::Week + } + + #[classattr] + #[pyo3(name = "MONTH")] + fn py_month() -> Self { + BarAggregation::Month + } +} + +#[pymethods] +impl BookAction { + #[classattr] + #[pyo3(name = "ADD")] + fn py_add() -> Self { + BookAction::Add + } + #[classattr] + #[pyo3(name = "UPDATE")] + fn py_update() -> Self { + BookAction::Update + } + #[classattr] + #[pyo3(name = "DELETE")] + fn py_delete() -> Self { + BookAction::Delete + } + #[classattr] + #[pyo3(name = "CLEAR")] + fn py_clear() -> Self { + BookAction::Clear + } +} + +#[pymethods] +impl ContingencyType { + #[classattr] + #[pyo3(name = "NO_CONTINGENCY")] + fn py_no_contingency() -> Self { + ContingencyType::NoContingency + } + #[classattr] + #[pyo3(name = "OCO")] + fn py_oco() -> Self { + ContingencyType::Oco + } + #[classattr] + #[pyo3(name = "OTO")] + fn py_oto() -> Self { + ContingencyType::Oto + } + #[classattr] + #[pyo3(name = "OUO")] + fn py_ouo() -> Self { + ContingencyType::Ouo + } +} + +#[pymethods] +impl CurrencyType { + #[classattr] + #[pyo3(name = "CRYPTO")] + fn py_crypto() -> Self { + CurrencyType::Crypto + } + #[classattr] + #[pyo3(name = "FIAT")] + fn py_fiat() -> Self { + CurrencyType::Fiat + } + #[classattr] + #[pyo3(name = "COMMODITY_BACKED")] + fn py_commodity_backed() -> Self { + CurrencyType::CommodityBacked + } +} + +#[pymethods] +impl InstrumentCloseType { + #[classattr] + #[pyo3(name = "END_OF_SESSION")] + fn py_end_of_session() -> Self { + InstrumentCloseType::EndOfSession + } + #[classattr] + #[pyo3(name = "CONTRACT_EXPIRED")] + fn py_contract_expired() -> Self { + InstrumentCloseType::ContractExpired + } +} + +#[pymethods] +impl LiquiditySide { + #[classattr] + #[pyo3(name = "NO_LIQUIDITY_SIDE")] + fn py_no_liquidity_side() -> Self { + LiquiditySide::NoLiquiditySide + } + #[classattr] + #[pyo3(name = "MAKER")] + fn py_maker() -> Self { + LiquiditySide::Maker + } + #[classattr] + #[pyo3(name = "TAKER")] + fn py_taker() -> Self { + LiquiditySide::Taker + } +} + +#[pymethods] +impl MarketStatus { + #[classattr] + #[pyo3(name = "PRE_OPEN")] + fn py_pre_open() -> Self { + MarketStatus::PreOpen + } + #[classattr] + #[pyo3(name = "OPEN")] + fn py_open() -> Self { + MarketStatus::Open + } + #[classattr] + #[pyo3(name = "PAUSE")] + fn py_pause() -> Self { + MarketStatus::Pause + } + #[classattr] + #[pyo3(name = "HALT")] + fn py_halt() -> Self { + MarketStatus::Halt + } + #[classattr] + #[pyo3(name = "REOPEN")] + fn py_reopen() -> Self { + MarketStatus::Reopen + } + #[classattr] + #[pyo3(name = "PRE_CLOSE")] + fn py_pre_close() -> Self { + MarketStatus::PreClose + } + #[classattr] + #[pyo3(name = "CLOSED")] + fn py_closed() -> Self { + MarketStatus::Closed + } +} + +#[pymethods] +impl HaltReason { + #[classattr] + #[pyo3(name = "NOT_HALTED")] + fn py_not_halted() -> Self { + HaltReason::NotHalted + } + #[classattr] + #[pyo3(name = "GENERAL")] + fn py_general() -> Self { + HaltReason::General + } + #[classattr] + #[pyo3(name = "VOLATILITY")] + fn py_volatility() -> Self { + HaltReason::Volatility + } +} + +#[pymethods] +impl OmsType { + #[classattr] + #[pyo3(name = "UNSPECIFIED")] + fn py_unspecified() -> Self { + OmsType::Unspecified + } + #[classattr] + #[pyo3(name = "NETTING")] + fn py_netting() -> Self { + OmsType::Netting + } + #[classattr] + #[pyo3(name = "HEDGING")] + fn py_hedging() -> Self { + OmsType::Hedging + } +} + +#[pymethods] +impl OptionKind { + #[classattr] + #[pyo3(name = "CALL")] + fn py_call() -> Self { + OptionKind::Call + } + + #[classattr] + #[pyo3(name = "PUT")] + fn py_put() -> Self { + OptionKind::Put + } +} + +#[pymethods] +impl OrderSide { + #[classattr] + #[pyo3(name = "NO_ORDER_SIDE")] + fn py_no_order_side() -> Self { + OrderSide::NoOrderSide + } + #[classattr] + #[pyo3(name = "BUY")] + fn py_buy() -> Self { + OrderSide::Buy + } + #[classattr] + #[pyo3(name = "SELL")] + fn py_sell() -> Self { + OrderSide::Sell + } +} + +#[pymethods] +impl OrderStatus { + #[classattr] + #[pyo3(name = "INITIALIZED")] + fn py_initialized() -> Self { + OrderStatus::Initialized + } + #[classattr] + #[pyo3(name = "DENIED")] + fn py_denied() -> Self { + OrderStatus::Denied + } + #[classattr] + #[pyo3(name = "EMULATED")] + fn py_emulated() -> Self { + OrderStatus::Emulated + } + #[classattr] + #[pyo3(name = "RELEASED")] + fn py_released() -> Self { + OrderStatus::Released + } + #[classattr] + #[pyo3(name = "SUBMITTED")] + fn py_submitted() -> Self { + OrderStatus::Submitted + } + #[classattr] + #[pyo3(name = "ACCEPTED")] + fn py_accepted() -> Self { + OrderStatus::Accepted + } + #[classattr] + #[pyo3(name = "REJECTED")] + fn py_rejected() -> Self { + OrderStatus::Rejected + } + #[classattr] + #[pyo3(name = "CANCELED")] + fn py_canceled() -> Self { + OrderStatus::Canceled + } + #[classattr] + #[pyo3(name = "EXPIRED")] + fn py_expired() -> Self { + OrderStatus::Expired + } + #[classattr] + #[pyo3(name = "TRIGGERED")] + fn py_triggered() -> Self { + OrderStatus::Triggered + } + #[classattr] + #[pyo3(name = "PENDING_UPDATE")] + fn py_pending_update() -> Self { + OrderStatus::PendingUpdate + } + #[classattr] + #[pyo3(name = "PENDING_CANCEL")] + fn py_pending_cancel() -> Self { + OrderStatus::PendingCancel + } + #[classattr] + #[pyo3(name = "PARTIALLY_FILLED")] + fn py_partially_filled() -> Self { + OrderStatus::PartiallyFilled + } + #[classattr] + #[pyo3(name = "FILLED")] + fn py_filled() -> Self { + OrderStatus::Filled + } +} + +#[pymethods] +impl OrderType { + #[classattr] + #[pyo3(name = "MARKET")] + fn py_market() -> Self { + OrderType::Market + } + #[classattr] + #[pyo3(name = "LIMIT")] + fn py_limit() -> Self { + OrderType::Limit + } + #[classattr] + #[pyo3(name = "STOP_MARKET")] + fn py_stop_market() -> Self { + OrderType::StopMarket + } + #[classattr] + #[pyo3(name = "STOP_LIMIT")] + fn py_stop_limit() -> Self { + OrderType::StopLimit + } + #[classattr] + #[pyo3(name = "MARKET_TO_LIMIT")] + fn py_market_to_limit() -> Self { + OrderType::MarketToLimit + } + #[classattr] + #[pyo3(name = "MARKET_IF_TOUCHED")] + fn py_market_if_touched() -> Self { + OrderType::MarketIfTouched + } + #[classattr] + #[pyo3(name = "LIMIT_IF_TOUCHED")] + fn py_limit_if_touched() -> Self { + OrderType::LimitIfTouched + } + #[classattr] + #[pyo3(name = "TRAILING_STOP_MARKET")] + fn py_trailing_stop_market() -> Self { + OrderType::TrailingStopMarket + } + #[classattr] + #[pyo3(name = "TRAILING_STOP_LIMIT")] + fn py_trailing_stop_limit() -> Self { + OrderType::TrailingStopLimit + } +} + +#[pymethods] +impl PositionSide { + #[classattr] + #[pyo3(name = "NO_POSITION_SIDE")] + fn py_no_position_side() -> Self { + PositionSide::NoPositionSide + } + #[classattr] + #[pyo3(name = "FLAT")] + fn py_flat() -> Self { + PositionSide::Flat + } + #[classattr] + #[pyo3(name = "LONG")] + fn py_long() -> Self { + PositionSide::Long + } + #[classattr] + #[pyo3(name = "SHORT")] + fn py_short() -> Self { + PositionSide::Short + } +} + +#[pymethods] +impl PriceType { + #[classattr] + #[pyo3(name = "BID")] + fn py_bid() -> Self { + PriceType::Bid + } + + #[classattr] + #[pyo3(name = "ASK")] + fn py_ask() -> Self { + PriceType::Ask + } + + #[classattr] + #[pyo3(name = "MID")] + fn py_mid() -> Self { + PriceType::Mid + } + + #[classattr] + #[pyo3(name = "LAST")] + fn py_last() -> Self { + PriceType::Last + } +} + +#[pymethods] +impl TimeInForce { + #[classattr] + #[pyo3(name = "GTC")] + fn py_gtc() -> Self { + TimeInForce::Gtc + } + #[classattr] + #[pyo3(name = "IOC")] + fn py_ioc() -> Self { + TimeInForce::Ioc + } + #[classattr] + #[pyo3(name = "FOK")] + fn py_fok() -> Self { + TimeInForce::Fok + } + #[classattr] + #[pyo3(name = "GTD")] + fn py_gtd() -> Self { + TimeInForce::Gtd + } + #[classattr] + #[pyo3(name = "DAY")] + fn py_day() -> Self { + TimeInForce::Day + } + #[classattr] + #[pyo3(name = "AT_THE_OPEN")] + fn py_at_the_open() -> Self { + TimeInForce::AtTheOpen + } + #[classattr] + #[pyo3(name = "AT_THE_CLOSE")] + fn py_at_the_close() -> Self { + TimeInForce::AtTheClose + } +} + +#[pymethods] +impl TrailingOffsetType { + #[classattr] + #[pyo3(name = "NO_TRAILING_OFFSET")] + fn py_no_trailing_offset() -> Self { + TrailingOffsetType::NoTrailingOffset + } + #[classattr] + #[pyo3(name = "PRICE")] + fn py_price() -> Self { + TrailingOffsetType::Price + } + #[classattr] + #[pyo3(name = "BASIS_POINTS")] + fn py_basis_points() -> Self { + TrailingOffsetType::BasisPoints + } + #[classattr] + #[pyo3(name = "TICKS")] + fn py_ticks() -> Self { + TrailingOffsetType::Ticks + } + #[classattr] + #[pyo3(name = "PRICE_TIER")] + fn py_price_tier() -> Self { + TrailingOffsetType::PriceTier + } +} + +#[pymethods] +impl TriggerType { + #[classattr] + #[pyo3(name = "NO_TRIGGER")] + fn py_no_trigger() -> Self { + TriggerType::NoTrigger + } + #[classattr] + #[pyo3(name = "DEFAULT")] + fn py_default() -> Self { + TriggerType::Default + } + #[classattr] + #[pyo3(name = "BID_ASK")] + fn py_bid_ask() -> Self { + TriggerType::BidAsk + } + #[classattr] + #[pyo3(name = "LAST_TRADE")] + fn py_last_trade() -> Self { + TriggerType::LastTrade + } + #[classattr] + #[pyo3(name = "DOUBLE_LAST")] + fn py_double_last() -> Self { + TriggerType::DoubleLast + } + #[classattr] + #[pyo3(name = "DOUBLE_BID_ASK")] + fn py_double_bid_ask() -> Self { + TriggerType::DoubleBidAsk + } + #[classattr] + #[pyo3(name = "LAST_OR_BID_ASK")] + fn py_last_or_bid_ask() -> Self { + TriggerType::LastOrBidAsk + } + #[classattr] + #[pyo3(name = "MID_POINT")] + fn py_mid_point() -> Self { + TriggerType::MidPoint + } + #[classattr] + #[pyo3(name = "MARK_PRICE")] + fn py_mark_price() -> Self { + TriggerType::MarkPrice + } + #[classattr] + #[pyo3(name = "INDEX_PRICE")] + fn py_index_price() -> Self { + TriggerType::IndexPrice + } +} + +#[pymethods] +impl BookType { + #[classattr] + #[pyo3(name = "L1_MBP")] + fn py_l1_mbp() -> Self { + BookType::L1_MBP + } + #[classattr] + #[pyo3(name = "L2_MBP")] + fn py_l2_mbp() -> Self { + BookType::L2_MBP + } + #[classattr] + #[pyo3(name = "L3_MBO")] + fn py_l3_mbo() -> Self { + BookType::L3_MBO + } +} + +#[pymethods] +impl TradingState { + #[classattr] + #[pyo3(name = "ACTIVE")] + fn py_active() -> Self { + TradingState::Active + } + #[classattr] + #[pyo3(name = "HALTED")] + fn py_halted() -> Self { + TradingState::Halted + } + #[classattr] + #[pyo3(name = "REDUCING")] + fn py_reducing() -> Self { + TradingState::Reducing + } +} + +enum_for_python!(AggregationSource); +enum_for_python!(AggressorSide); +enum_for_python!(AssetClass); +enum_for_python!(BarAggregation); +enum_for_python!(BookAction); +enum_for_python!(BookType); +enum_for_python!(ContingencyType); +enum_for_python!(CurrencyType); +enum_for_python!(InstrumentCloseType); +enum_for_python!(LiquiditySide); +enum_for_python!(MarketStatus); +enum_for_python!(OmsType); +enum_for_python!(OptionKind); +enum_for_python!(OrderSide); +enum_for_python!(OrderStatus); +enum_for_python!(OrderType); +enum_for_python!(PositionSide); +enum_for_python!(PriceType); +enum_for_python!(TimeInForce); +enum_for_python!(TradingState); +enum_for_python!(TrailingOffsetType); +enum_for_python!(TriggerType); diff --git a/nautilus_core/model/src/python/events/order/filled.rs b/nautilus_core/model/src/python/events/order/filled.rs index 07413c29d86d..56819c558a75 100644 --- a/nautilus_core/model/src/python/events/order/filled.rs +++ b/nautilus_core/model/src/python/events/order/filled.rs @@ -193,6 +193,120 @@ impl OrderFilled { } } + #[getter] + #[pyo3(name = "trader_id")] + fn py_trader_id(&self) -> TraderId { + self.trader_id + } + + #[getter] + #[pyo3(name = "instrument_id")] + fn py_instrument_id(&self) -> InstrumentId { + self.instrument_id + } + + #[getter] + #[pyo3(name = "strategy_id")] + fn py_strategy_id(&self) -> StrategyId { + self.strategy_id + } + + #[getter] + #[pyo3(name = "client_order_id")] + fn py_client_order_id(&self) -> ClientOrderId { + self.client_order_id + } + + #[getter] + #[pyo3(name = "venue_order_id")] + fn py_venue_order_id(&self) -> VenueOrderId { + self.venue_order_id + } + + #[getter] + #[pyo3(name = "account_id")] + fn py_account_id(&self) -> AccountId { + self.account_id + } + + #[getter] + #[pyo3(name = "trade_id")] + fn py_trade_id(&self) -> TradeId { + self.trade_id + } + + #[getter] + #[pyo3(name = "order_side")] + fn py_order_side(&self) -> OrderSide { + self.order_side + } + + #[getter] + #[pyo3(name = "last_qty")] + fn py_last_qty(&self) -> Quantity { + self.last_qty + } + + #[getter] + #[pyo3(name = "last_px")] + fn py_last_px(&self) -> Price { + self.last_px + } + + #[getter] + #[pyo3(name = "currency")] + fn py_currency(&self) -> Currency { + self.currency + } + + #[getter] + #[pyo3(name = "liquidity_side")] + fn py_liquidity_side(&self) -> LiquiditySide { + self.liquidity_side + } + + #[getter] + #[pyo3(name = "event_id")] + fn py_event_id(&self) -> UUID4 { + self.event_id + } + + #[getter] + #[pyo3(name = "ts_event")] + fn py_ts_event(&self) -> UnixNanos { + self.ts_event + } + + #[getter] + #[pyo3(name = "ts_init")] + fn py_ts_init(&self) -> UnixNanos { + self.ts_init + } + + #[getter] + #[pyo3(name = "reconciliation")] + fn py_reconciliation(&self) -> bool { + self.reconciliation + } + + #[getter] + #[pyo3(name = "position_id")] + fn py_position_id(&self) -> Option { + self.position_id + } + + #[getter] + #[pyo3(name = "commission")] + fn py_commission(&self) -> Option { + self.commission + } + + #[getter] + #[pyo3(name = "order_type")] + fn py_order_type(&self) -> OrderType { + self.order_type + } + #[staticmethod] #[pyo3(name = "from_dict")] fn py_from_dict(py: Python<'_>, values: Py) -> PyResult { diff --git a/nautilus_core/model/src/python/identifiers/mod.rs b/nautilus_core/model/src/python/identifiers/mod.rs index 92c4c0ce43ce..df96506a4440 100644 --- a/nautilus_core/model/src/python/identifiers/mod.rs +++ b/nautilus_core/model/src/python/identifiers/mod.rs @@ -13,5 +13,28 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- +use crate::identifier_for_python; +use nautilus_core::python::to_pyvalue_err; +use pyo3::{ + prelude::*, + pyclass::CompareOp, + types::{PyString, PyTuple}, +}; +use std::str::FromStr; +use ustr::Ustr; + pub mod instrument_id; pub mod trade_id; + +identifier_for_python!(crate::identifiers::account_id::AccountId); +identifier_for_python!(crate::identifiers::client_id::ClientId); +identifier_for_python!(crate::identifiers::client_order_id::ClientOrderId); +identifier_for_python!(crate::identifiers::component_id::ComponentId); +identifier_for_python!(crate::identifiers::exec_algorithm_id::ExecAlgorithmId); +identifier_for_python!(crate::identifiers::order_list_id::OrderListId); +identifier_for_python!(crate::identifiers::position_id::PositionId); +identifier_for_python!(crate::identifiers::strategy_id::StrategyId); +identifier_for_python!(crate::identifiers::symbol::Symbol); +identifier_for_python!(crate::identifiers::trader_id::TraderId); +identifier_for_python!(crate::identifiers::venue::Venue); +identifier_for_python!(crate::identifiers::venue_order_id::VenueOrderId); diff --git a/nautilus_core/model/src/python/mod.rs b/nautilus_core/model/src/python/mod.rs index 640ed4862bb0..028c20b0c80c 100644 --- a/nautilus_core/model/src/python/mod.rs +++ b/nautilus_core/model/src/python/mod.rs @@ -15,10 +15,9 @@ use pyo3::prelude::*; -use crate::enums; - pub mod common; pub mod data; +pub mod enums; pub mod events; pub mod identifiers; pub mod instruments; @@ -43,30 +42,30 @@ pub fn model(_: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; // Enums - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; // Identifiers m.add_class::()?; m.add_class::()?; diff --git a/nautilus_core/model/src/python/orders/market.rs b/nautilus_core/model/src/python/orders/market.rs index a78341ad13d3..1af77add52aa 100644 --- a/nautilus_core/model/src/python/orders/market.rs +++ b/nautilus_core/model/src/python/orders/market.rs @@ -13,10 +13,10 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- +use pyo3::{pymethods, PyResult}; use std::collections::HashMap; use nautilus_core::{python::to_pyvalue_err, time::UnixNanos, uuid::UUID4}; -use pyo3::prelude::*; use rust_decimal::Decimal; use ustr::Ustr; diff --git a/nautilus_core/model/src/types/mod.rs b/nautilus_core/model/src/types/mod.rs index 5d2980df3f34..f5e1c1e4ca32 100644 --- a/nautilus_core/model/src/types/mod.rs +++ b/nautilus_core/model/src/types/mod.rs @@ -19,4 +19,5 @@ pub mod fixed; pub mod money; pub mod price; pub mod quantity; +#[cfg(feature = "stubs")] pub mod stubs; diff --git a/nautilus_core/persistence/Cargo.toml b/nautilus_core/persistence/Cargo.toml index a2130cd13e71..0c741aaa7e98 100644 --- a/nautilus_core/persistence/Cargo.toml +++ b/nautilus_core/persistence/Cargo.toml @@ -10,17 +10,9 @@ documentation.workspace = true name = "nautilus_persistence" crate-type = ["rlib", "staticlib", "cdylib"] -[[bin]] -name = "init-db" -path = "src/bin/init_db.rs" - -[[bin]] -name = "drop-db" -path = "src/bin/drop_db.rs" - [dependencies] nautilus-core = { path = "../core" } -nautilus-model = { path = "../model", features = ["stubs"]} +nautilus-model = { path = "../model" } anyhow = { workspace = true } futures = { workspace = true } pyo3 = { workspace = true, optional = true } @@ -33,15 +25,6 @@ datafusion = { version = "36.0.0", default-features = false, features = ["compre dotenv = "0.15.0" sqlx = { version = "0.7.3", features = ["sqlite", "postgres", "any", "runtime-tokio"] } -[features] -extension-module = [ - "pyo3/extension-module", - "nautilus-core/extension-module", - "nautilus-model/extension-module", -] -python = ["pyo3"] -default = ["python"] - [dev-dependencies] criterion = { workspace = true } rstest = { workspace = true } @@ -50,6 +33,16 @@ quickcheck_macros = "1" [target.'cfg(target_os = "linux")'.dependencies] procfs = "0.16.0" +[features] +extension-module = [ + "pyo3/extension-module", + "nautilus-core/extension-module", + "nautilus-model/extension-module", +] +ffi = ["nautilus-core/ffi", "nautilus-model/ffi"] +python = ["pyo3", "nautilus-core/python", "nautilus-model/python"] +default = ["ffi", "python"] + [[bench]] name = "bench_persistence" harness = false diff --git a/nautilus_core/persistence/src/bin/init_db.rs b/nautilus_core/persistence/src/bin/init_db.rs deleted file mode 100644 index adb67b681252..000000000000 --- a/nautilus_core/persistence/src/bin/init_db.rs +++ /dev/null @@ -1,28 +0,0 @@ -// ------------------------------------------------------------------------------------------------- -// 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 anyhow::Result; -use dotenv::dotenv; -use nautilus_persistence::db::database::{init_db_schema, Database}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - // load envs if exists - dotenv().ok(); - let db = Database::new(None, None).await; - let sql_schema_dir = "../schema"; - init_db_schema(&db, sql_schema_dir).await?; - Ok(()) -} diff --git a/nautilus_core/pyo3/Cargo.toml b/nautilus_core/pyo3/Cargo.toml index 358818e9a8d6..dc3713f369db 100644 --- a/nautilus_core/pyo3/Cargo.toml +++ b/nautilus_core/pyo3/Cargo.toml @@ -11,15 +11,15 @@ name = "nautilus_pyo3" crate-type = ["cdylib"] [dependencies] -nautilus-adapters = { path = "../adapters" } -nautilus-core = { path = "../core" } -nautilus-common = { path = "../common" } -nautilus-indicators = { path = "../indicators" } -nautilus-infrastructure = { path = "../infrastructure" } -nautilus-model = { path = "../model" } -nautilus-persistence = { path = "../persistence" } -nautilus-network = { path = "../network" } -nautilus-accounting = { path = "../accounting" } +nautilus-adapters = { path = "../adapters", features = ["databento"] } +nautilus-core = { path = "../core" , features = ["python"] } +nautilus-common = { path = "../common" , features = ["python"] } +nautilus-indicators = { path = "../indicators" , features = ["python"] } +nautilus-infrastructure = { path = "../infrastructure", features = ["python"] } +nautilus-model = { path = "../model" , features = ["python"] } +nautilus-persistence = { path = "../persistence" , features = ["python"] } +nautilus-network = { path = "../network" , features = ["python"] } +nautilus-accounting = { path = "../accounting", features = ["python"] } pyo3 = { workspace = true } [features] @@ -33,4 +33,11 @@ extension-module = [ "nautilus-model/extension-module", "nautilus-persistence/extension-module", ] +ffi = [ + "nautilus-adapters/ffi", + "nautilus-core/ffi", + "nautilus-common/ffi", + "nautilus-model/ffi", + "nautilus-persistence/ffi", +] default = [] diff --git a/tests/conftest.py b/tests/conftest.py index f7cedfc67294..6f1c32df080a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,7 +36,7 @@ def bypass_logging() -> None: """ init_logging( level_stdout=LogLevel.WARNING, - bypass=False, + bypass=True, # Set this to False to see logging in tests )