nautilus_sandbox/
config.rs1use ahash::AHashMap;
19use nautilus_execution::{
20 matching_engine::config::OrderMatchingEngineConfig, models::fee::FeeModelAny,
21};
22use nautilus_model::{
23 enums::{AccountType, BookType, OmsType},
24 identifiers::{AccountId, InstrumentId, TraderId, Venue},
25 types::{Currency, Money},
26};
27use rust_decimal::Decimal;
28use serde::{
29 Deserialize, Deserializer, Serialize, Serializer,
30 de::{self, IgnoredAny},
31};
32
33#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
35#[serde(default, deny_unknown_fields)]
36#[cfg_attr(
37 feature = "python",
38 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.sandbox", from_py_object)
39)]
40#[cfg_attr(
41 feature = "python",
42 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.sandbox")
43)]
44pub struct SandboxExecutionClientConfig {
45 #[builder(default = TraderId::from("SANDBOX-001"))]
47 pub trader_id: TraderId,
48 #[builder(default = AccountId::from("SANDBOX-001"))]
50 pub account_id: AccountId,
51 #[builder(default = Venue::new("SANDBOX"))]
53 pub venue: Venue,
54 #[builder(default)]
56 pub starting_balances: Vec<Money>,
57 pub base_currency: Option<Currency>,
59 #[builder(default = OmsType::Netting)]
61 pub oms_type: OmsType,
62 #[builder(default = AccountType::Margin)]
64 pub account_type: AccountType,
65 #[builder(default = Decimal::ONE)]
67 pub default_leverage: Decimal,
68 #[builder(default)]
70 pub leverages: AHashMap<InstrumentId, Decimal>,
71 #[builder(default = BookType::L1_MBP)]
73 pub book_type: BookType,
74 #[serde(
76 default,
77 skip_serializing_if = "Option::is_none",
78 serialize_with = "serialize_fee_model",
79 deserialize_with = "deserialize_fee_model"
80 )]
81 pub fee_model: Option<FeeModelAny>,
82 #[builder(default)]
84 pub frozen_account: bool,
85 #[builder(default = true)]
87 pub bar_execution: bool,
88 #[builder(default = true)]
90 pub trade_execution: bool,
91 #[builder(default = true)]
93 pub reject_stop_orders: bool,
94 #[builder(default = true)]
96 pub support_gtd_orders: bool,
97 #[builder(default = true)]
99 pub support_contingent_orders: bool,
100 #[builder(default = true)]
102 pub use_position_ids: bool,
103 #[builder(default)]
106 pub use_random_ids: bool,
107 #[builder(default = true)]
109 pub use_reduce_only: bool,
110}
111
112impl SandboxExecutionClientConfig {
113 #[must_use]
115 pub fn to_matching_engine_config(&self) -> OrderMatchingEngineConfig {
116 OrderMatchingEngineConfig::builder()
117 .bar_execution(self.bar_execution)
118 .trade_execution(self.trade_execution)
119 .reject_stop_orders(self.reject_stop_orders)
120 .support_gtd_orders(self.support_gtd_orders)
121 .support_contingent_orders(self.support_contingent_orders)
122 .use_position_ids(self.use_position_ids)
123 .use_random_ids(self.use_random_ids)
124 .use_reduce_only(self.use_reduce_only)
125 .build()
126 }
127}
128
129impl Default for SandboxExecutionClientConfig {
130 fn default() -> Self {
131 Self::builder().build()
132 }
133}
134
135fn serialize_fee_model<S>(fee_model: &Option<FeeModelAny>, serializer: S) -> Result<S::Ok, S::Error>
136where
137 S: Serializer,
138{
139 match fee_model {
140 None => serializer.serialize_none(),
141 Some(_) => Err(serde::ser::Error::custom(
142 "SandboxExecutionClientConfig.fee_model is runtime-only and cannot be serialized",
143 )),
144 }
145}
146
147fn deserialize_fee_model<'de, D>(deserializer: D) -> Result<Option<FeeModelAny>, D::Error>
148where
149 D: Deserializer<'de>,
150{
151 let value = Option::<IgnoredAny>::deserialize(deserializer)?;
152
153 match value {
154 None => Ok(None),
155 Some(_) => Err(de::Error::custom(
156 "SandboxExecutionClientConfig.fee_model must be configured at runtime, not deserialized",
157 )),
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use nautilus_execution::models::fee::{FeeModelAny, ProbabilityPriceFeeModel};
164 use rstest::rstest;
165
166 use super::*;
167
168 #[rstest]
169 fn test_exec_config_toml_empty_uses_defaults() {
170 let config: SandboxExecutionClientConfig = toml::from_str("").unwrap();
171 let expected = SandboxExecutionClientConfig::default();
172
173 assert_eq!(config.trader_id, expected.trader_id);
174 assert_eq!(config.account_id, expected.account_id);
175 assert_eq!(config.venue, expected.venue);
176 assert_eq!(config.oms_type, expected.oms_type);
177 assert_eq!(config.account_type, expected.account_type);
178 assert_eq!(config.default_leverage, expected.default_leverage);
179 assert_eq!(config.book_type, expected.book_type);
180 assert!(config.fee_model.is_none());
181 assert_eq!(config.bar_execution, expected.bar_execution);
182 assert_eq!(config.trade_execution, expected.trade_execution);
183 assert_eq!(config.use_position_ids, expected.use_position_ids);
184 }
185
186 #[rstest]
187 fn test_exec_config_toml_rejects_fee_model_field() {
188 let result = toml::from_str::<SandboxExecutionClientConfig>("fee_model = \"runtime-only\"");
189
190 assert!(result.is_err());
191 }
192
193 #[rstest]
194 fn test_exec_config_toml_rejects_serializing_runtime_fee_model() {
195 let config = SandboxExecutionClientConfig {
196 fee_model: Some(FeeModelAny::ProbabilityPrice(ProbabilityPriceFeeModel)),
197 ..SandboxExecutionClientConfig::default()
198 };
199
200 let result = toml::Value::try_from(&config);
201
202 assert!(result.is_err());
203 }
204}