Skip to main content

nautilus_trading/examples/strategies/grid_mm/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Configuration for the grid market making strategy.
17
18use nautilus_model::{
19    identifiers::{InstrumentId, StrategyId},
20    types::Quantity,
21};
22
23use crate::strategy::StrategyConfig;
24
25/// Configuration for the grid market making strategy.
26#[derive(Debug, Clone)]
27#[cfg_attr(
28    feature = "python",
29    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.trading", from_py_object)
30)]
31pub struct GridMarketMakerConfig {
32    /// Base strategy configuration.
33    pub base: StrategyConfig,
34    /// Instrument ID to trade.
35    pub instrument_id: InstrumentId,
36    /// Trade size per grid level. When `None` the strategy resolves it from
37    /// the instrument's `min_quantity` during `on_start`.
38    pub trade_size: Option<Quantity>,
39    /// Number of price levels on each side (buy & sell).
40    pub num_levels: usize,
41    /// Grid spacing in basis points of mid-price (geometric grid).
42    /// E.g. `10` = 10 bps = 0.1%. Buy level N = mid × (1 - bps/10000)^N.
43    pub grid_step_bps: u32,
44    /// How aggressively to shift the grid based on inventory.
45    pub skew_factor: f64,
46    /// Hard cap on net exposure (long or short).
47    pub max_position: Quantity,
48    /// Minimum mid-price move in basis points before re-quoting.
49    /// E.g. `5` = 5 bps = 0.05%.
50    pub requote_threshold_bps: u32,
51    /// Optional order expiry in seconds. When set, orders use GTD
52    /// time-in-force with `expire_time = now + expire_time_secs`.
53    pub expire_time_secs: Option<u64>,
54    /// When `true`, resubmit the full grid on the next quote after receiving
55    /// an order cancel event. Useful for exchanges like dYdX where short-term
56    /// orders are canceled by the protocol after expiry.
57    pub on_cancel_resubmit: bool,
58}
59
60impl GridMarketMakerConfig {
61    /// Creates a new [`GridMarketMakerConfig`] with required fields and sensible defaults.
62    #[must_use]
63    pub fn new(instrument_id: InstrumentId, max_position: Quantity) -> Self {
64        Self {
65            base: StrategyConfig {
66                strategy_id: Some(StrategyId::from("GRID_MM-001")),
67                order_id_tag: Some("001".to_string()),
68                ..Default::default()
69            },
70            instrument_id,
71            trade_size: None,
72            num_levels: 3,
73            grid_step_bps: 10,
74            skew_factor: 0.0,
75            max_position,
76            requote_threshold_bps: 5,
77            expire_time_secs: None,
78            on_cancel_resubmit: false,
79        }
80    }
81
82    #[must_use]
83    pub fn with_trade_size(mut self, trade_size: Quantity) -> Self {
84        self.trade_size = Some(trade_size);
85        self
86    }
87
88    #[must_use]
89    pub fn with_num_levels(mut self, num_levels: usize) -> Self {
90        self.num_levels = num_levels;
91        self
92    }
93
94    #[must_use]
95    pub fn with_grid_step_bps(mut self, bps: u32) -> Self {
96        self.grid_step_bps = bps;
97        self
98    }
99
100    #[must_use]
101    pub fn with_skew_factor(mut self, skew_factor: f64) -> Self {
102        self.skew_factor = skew_factor;
103        self
104    }
105
106    #[must_use]
107    pub fn with_requote_threshold_bps(mut self, bps: u32) -> Self {
108        self.requote_threshold_bps = bps;
109        self
110    }
111
112    #[must_use]
113    pub fn with_expire_time_secs(mut self, secs: u64) -> Self {
114        self.expire_time_secs = Some(secs);
115        self
116    }
117
118    #[must_use]
119    pub fn with_on_cancel_resubmit(mut self, enabled: bool) -> Self {
120        self.on_cancel_resubmit = enabled;
121        self
122    }
123
124    #[must_use]
125    pub fn with_strategy_id(mut self, strategy_id: StrategyId) -> Self {
126        self.base.strategy_id = Some(strategy_id);
127        self
128    }
129
130    #[must_use]
131    pub fn with_order_id_tag(mut self, tag: String) -> Self {
132        self.base.order_id_tag = Some(tag);
133        self
134    }
135}
136
137#[cfg(feature = "python")]
138#[pyo3::pymethods]
139impl GridMarketMakerConfig {
140    #[new]
141    #[pyo3(signature = (
142        instrument_id,
143        max_position,
144        strategy_id=None,
145        order_id_tag=None,
146        trade_size=None,
147        num_levels=3,
148        grid_step_bps=10,
149        skew_factor=0.0,
150        requote_threshold_bps=5,
151        expire_time_secs=None,
152        on_cancel_resubmit=false,
153    ))]
154    #[expect(clippy::too_many_arguments)]
155    fn py_new(
156        instrument_id: InstrumentId,
157        max_position: Quantity,
158        strategy_id: Option<StrategyId>,
159        order_id_tag: Option<String>,
160        trade_size: Option<Quantity>,
161        num_levels: usize,
162        grid_step_bps: u32,
163        skew_factor: f64,
164        requote_threshold_bps: u32,
165        expire_time_secs: Option<u64>,
166        on_cancel_resubmit: bool,
167    ) -> Self {
168        let mut config = Self::new(instrument_id, max_position)
169            .with_num_levels(num_levels)
170            .with_grid_step_bps(grid_step_bps)
171            .with_skew_factor(skew_factor)
172            .with_requote_threshold_bps(requote_threshold_bps)
173            .with_on_cancel_resubmit(on_cancel_resubmit);
174
175        if let Some(size) = trade_size {
176            config = config.with_trade_size(size);
177        }
178
179        if let Some(secs) = expire_time_secs {
180            config = config.with_expire_time_secs(secs);
181        }
182
183        if let Some(id) = strategy_id {
184            config.base.strategy_id = Some(id);
185        }
186
187        if let Some(tag) = order_id_tag {
188            config.base.order_id_tag = Some(tag);
189        }
190
191        config
192    }
193
194    #[getter]
195    fn instrument_id(&self) -> InstrumentId {
196        self.instrument_id
197    }
198
199    #[getter]
200    fn max_position(&self) -> Quantity {
201        self.max_position
202    }
203
204    #[getter]
205    fn trade_size(&self) -> Option<Quantity> {
206        self.trade_size
207    }
208
209    #[getter]
210    fn num_levels(&self) -> usize {
211        self.num_levels
212    }
213
214    #[getter]
215    fn grid_step_bps(&self) -> u32 {
216        self.grid_step_bps
217    }
218
219    #[getter]
220    fn skew_factor(&self) -> f64 {
221        self.skew_factor
222    }
223
224    #[getter]
225    fn requote_threshold_bps(&self) -> u32 {
226        self.requote_threshold_bps
227    }
228
229    #[getter]
230    fn expire_time_secs(&self) -> Option<u64> {
231        self.expire_time_secs
232    }
233
234    #[getter]
235    fn on_cancel_resubmit(&self) -> bool {
236        self.on_cancel_resubmit
237    }
238}