nautilus_bybit/python/
params.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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
16use pyo3::prelude::*;
17use serde_json;
18use ustr::Ustr;
19
20use crate::{
21    common::enums::{
22        BybitOrderSide, BybitOrderType, BybitProductType, BybitTimeInForce, BybitTriggerType,
23    },
24    websocket::{error::BybitWsError, messages},
25};
26
27/// Parameters for placing an order via WebSocket.
28#[pyclass]
29#[derive(Clone, Debug)]
30pub struct BybitWsPlaceOrderParams {
31    #[pyo3(get, set)]
32    pub category: BybitProductType,
33    #[pyo3(get, set)]
34    pub symbol: String,
35    #[pyo3(get, set)]
36    pub side: String,
37    #[pyo3(get, set)]
38    pub order_type: String,
39    #[pyo3(get, set)]
40    pub qty: String,
41    #[pyo3(get, set)]
42    pub is_leverage: Option<i32>,
43    #[pyo3(get, set)]
44    pub market_unit: Option<String>,
45    #[pyo3(get, set)]
46    pub price: Option<String>,
47    #[pyo3(get, set)]
48    pub time_in_force: Option<String>,
49    #[pyo3(get, set)]
50    pub order_link_id: Option<String>,
51    #[pyo3(get, set)]
52    pub reduce_only: Option<bool>,
53    #[pyo3(get, set)]
54    pub close_on_trigger: Option<bool>,
55    #[pyo3(get, set)]
56    pub trigger_price: Option<String>,
57    #[pyo3(get, set)]
58    pub trigger_by: Option<String>,
59    #[pyo3(get, set)]
60    pub trigger_direction: Option<i32>,
61    #[pyo3(get, set)]
62    pub tpsl_mode: Option<String>,
63    #[pyo3(get, set)]
64    pub take_profit: Option<String>,
65    #[pyo3(get, set)]
66    pub stop_loss: Option<String>,
67    #[pyo3(get, set)]
68    pub tp_trigger_by: Option<String>,
69    #[pyo3(get, set)]
70    pub sl_trigger_by: Option<String>,
71    #[pyo3(get, set)]
72    pub sl_trigger_price: Option<String>,
73    #[pyo3(get, set)]
74    pub tp_trigger_price: Option<String>,
75    #[pyo3(get, set)]
76    pub sl_order_type: Option<String>,
77    #[pyo3(get, set)]
78    pub tp_order_type: Option<String>,
79    #[pyo3(get, set)]
80    pub sl_limit_price: Option<String>,
81    #[pyo3(get, set)]
82    pub tp_limit_price: Option<String>,
83}
84
85#[pymethods]
86impl BybitWsPlaceOrderParams {
87    #[new]
88    #[allow(clippy::too_many_arguments)]
89    fn py_new(
90        category: BybitProductType,
91        symbol: String,
92        side: String,
93        order_type: String,
94        qty: String,
95        is_leverage: Option<i32>,
96        market_unit: Option<String>,
97        price: Option<String>,
98        time_in_force: Option<String>,
99        order_link_id: Option<String>,
100        reduce_only: Option<bool>,
101        close_on_trigger: Option<bool>,
102        trigger_price: Option<String>,
103        trigger_by: Option<String>,
104        trigger_direction: Option<i32>,
105        tpsl_mode: Option<String>,
106        take_profit: Option<String>,
107        stop_loss: Option<String>,
108        tp_trigger_by: Option<String>,
109        sl_trigger_by: Option<String>,
110        sl_trigger_price: Option<String>,
111        tp_trigger_price: Option<String>,
112        sl_order_type: Option<String>,
113        tp_order_type: Option<String>,
114        sl_limit_price: Option<String>,
115        tp_limit_price: Option<String>,
116    ) -> Self {
117        Self {
118            category,
119            symbol,
120            side,
121            order_type,
122            qty,
123            is_leverage,
124            market_unit,
125            price,
126            time_in_force,
127            order_link_id,
128            reduce_only,
129            close_on_trigger,
130            trigger_price,
131            trigger_by,
132            trigger_direction,
133            tpsl_mode,
134            take_profit,
135            stop_loss,
136            tp_trigger_by,
137            sl_trigger_by,
138            sl_trigger_price,
139            tp_trigger_price,
140            sl_order_type,
141            tp_order_type,
142            sl_limit_price,
143            tp_limit_price,
144        }
145    }
146}
147
148impl TryFrom<BybitWsPlaceOrderParams> for messages::BybitWsPlaceOrderParams {
149    type Error = BybitWsError;
150
151    fn try_from(params: BybitWsPlaceOrderParams) -> Result<Self, Self::Error> {
152        let side: BybitOrderSide =
153            serde_json::from_str(&format!("\"{}\"", params.side)).map_err(|e| {
154                BybitWsError::ClientError(format!("Invalid side '{}': {}", params.side, e))
155            })?;
156        let order_type: BybitOrderType =
157            serde_json::from_str(&format!("\"{}\"", params.order_type)).map_err(|e| {
158                BybitWsError::ClientError(format!(
159                    "Invalid order_type '{}': {}",
160                    params.order_type, e
161                ))
162            })?;
163
164        let time_in_force = params
165            .time_in_force
166            .map(|v| {
167                serde_json::from_str::<BybitTimeInForce>(&format!("\"{}\"", v)).map_err(|e| {
168                    BybitWsError::ClientError(format!("Invalid time_in_force '{}': {}", v, e))
169                })
170            })
171            .transpose()?;
172
173        let trigger_by = params
174            .trigger_by
175            .map(|v| {
176                serde_json::from_str::<BybitTriggerType>(&format!("\"{}\"", v)).map_err(|e| {
177                    BybitWsError::ClientError(format!("Invalid trigger_by '{}': {}", v, e))
178                })
179            })
180            .transpose()?;
181
182        let tp_trigger_by = params
183            .tp_trigger_by
184            .map(|v| {
185                serde_json::from_str::<BybitTriggerType>(&format!("\"{}\"", v)).map_err(|e| {
186                    BybitWsError::ClientError(format!("Invalid tp_trigger_by '{}': {}", v, e))
187                })
188            })
189            .transpose()?;
190
191        let sl_trigger_by = params
192            .sl_trigger_by
193            .map(|v| {
194                serde_json::from_str::<BybitTriggerType>(&format!("\"{}\"", v)).map_err(|e| {
195                    BybitWsError::ClientError(format!("Invalid sl_trigger_by '{}': {}", v, e))
196                })
197            })
198            .transpose()?;
199
200        let sl_order_type = params
201            .sl_order_type
202            .map(|v| {
203                serde_json::from_str::<BybitOrderType>(&format!("\"{}\"", v)).map_err(|e| {
204                    BybitWsError::ClientError(format!("Invalid sl_order_type '{}': {}", v, e))
205                })
206            })
207            .transpose()?;
208
209        let tp_order_type = params
210            .tp_order_type
211            .map(|v| {
212                serde_json::from_str::<BybitOrderType>(&format!("\"{}\"", v)).map_err(|e| {
213                    BybitWsError::ClientError(format!("Invalid tp_order_type '{}': {}", v, e))
214                })
215            })
216            .transpose()?;
217
218        Ok(Self {
219            category: params.category,
220            symbol: Ustr::from(&params.symbol),
221            side,
222            order_type,
223            qty: params.qty,
224            is_leverage: params.is_leverage,
225            market_unit: params.market_unit,
226            price: params.price,
227            time_in_force,
228            order_link_id: params.order_link_id,
229            reduce_only: params.reduce_only,
230            close_on_trigger: params.close_on_trigger,
231            trigger_price: params.trigger_price,
232            trigger_by,
233            trigger_direction: params.trigger_direction,
234            tpsl_mode: params.tpsl_mode,
235            take_profit: params.take_profit,
236            stop_loss: params.stop_loss,
237            tp_trigger_by,
238            sl_trigger_by,
239            sl_trigger_price: params.sl_trigger_price,
240            tp_trigger_price: params.tp_trigger_price,
241            sl_order_type,
242            tp_order_type,
243            sl_limit_price: params.sl_limit_price,
244            tp_limit_price: params.tp_limit_price,
245        })
246    }
247}
248
249impl From<messages::BybitWsPlaceOrderParams> for BybitWsPlaceOrderParams {
250    fn from(params: messages::BybitWsPlaceOrderParams) -> Self {
251        let side = serde_json::to_string(&params.side)
252            .expect("Failed to serialize BybitOrderSide")
253            .trim_matches('"')
254            .to_string();
255        let order_type = serde_json::to_string(&params.order_type)
256            .expect("Failed to serialize BybitOrderType")
257            .trim_matches('"')
258            .to_string();
259        let time_in_force = params.time_in_force.map(|v| {
260            serde_json::to_string(&v)
261                .expect("Failed to serialize BybitTimeInForce")
262                .trim_matches('"')
263                .to_string()
264        });
265        let trigger_by = params.trigger_by.map(|v| {
266            serde_json::to_string(&v)
267                .expect("Failed to serialize BybitTriggerType")
268                .trim_matches('"')
269                .to_string()
270        });
271        let tp_trigger_by = params.tp_trigger_by.map(|v| {
272            serde_json::to_string(&v)
273                .expect("Failed to serialize BybitTriggerType")
274                .trim_matches('"')
275                .to_string()
276        });
277        let sl_trigger_by = params.sl_trigger_by.map(|v| {
278            serde_json::to_string(&v)
279                .expect("Failed to serialize BybitTriggerType")
280                .trim_matches('"')
281                .to_string()
282        });
283        let sl_order_type = params.sl_order_type.map(|v| {
284            serde_json::to_string(&v)
285                .expect("Failed to serialize BybitOrderType")
286                .trim_matches('"')
287                .to_string()
288        });
289        let tp_order_type = params.tp_order_type.map(|v| {
290            serde_json::to_string(&v)
291                .expect("Failed to serialize BybitOrderType")
292                .trim_matches('"')
293                .to_string()
294        });
295
296        Self {
297            category: params.category,
298            symbol: params.symbol.to_string(),
299            side,
300            order_type,
301            qty: params.qty,
302            is_leverage: params.is_leverage,
303            market_unit: params.market_unit,
304            price: params.price,
305            time_in_force,
306            order_link_id: params.order_link_id,
307            reduce_only: params.reduce_only,
308            close_on_trigger: params.close_on_trigger,
309            trigger_price: params.trigger_price,
310            trigger_by,
311            trigger_direction: params.trigger_direction,
312            tpsl_mode: params.tpsl_mode,
313            take_profit: params.take_profit,
314            stop_loss: params.stop_loss,
315            tp_trigger_by,
316            sl_trigger_by,
317            sl_trigger_price: params.sl_trigger_price,
318            tp_trigger_price: params.tp_trigger_price,
319            sl_order_type,
320            tp_order_type,
321            sl_limit_price: params.sl_limit_price,
322            tp_limit_price: params.tp_limit_price,
323        }
324    }
325}
326
327/// Parameters for amending an order via WebSocket.
328#[pyclass]
329#[derive(Clone, Debug)]
330pub struct BybitWsAmendOrderParams {
331    #[pyo3(get, set)]
332    pub category: BybitProductType,
333    #[pyo3(get, set)]
334    pub symbol: String,
335    #[pyo3(get, set)]
336    pub order_id: Option<String>,
337    #[pyo3(get, set)]
338    pub order_link_id: Option<String>,
339    #[pyo3(get, set)]
340    pub qty: Option<String>,
341    #[pyo3(get, set)]
342    pub price: Option<String>,
343    #[pyo3(get, set)]
344    pub trigger_price: Option<String>,
345    #[pyo3(get, set)]
346    pub take_profit: Option<String>,
347    #[pyo3(get, set)]
348    pub stop_loss: Option<String>,
349    #[pyo3(get, set)]
350    pub tp_trigger_by: Option<String>,
351    #[pyo3(get, set)]
352    pub sl_trigger_by: Option<String>,
353}
354
355#[pymethods]
356impl BybitWsAmendOrderParams {
357    #[new]
358    #[allow(clippy::too_many_arguments)]
359    fn py_new(
360        category: BybitProductType,
361        symbol: String,
362        order_id: Option<String>,
363        order_link_id: Option<String>,
364        qty: Option<String>,
365        price: Option<String>,
366        trigger_price: Option<String>,
367        take_profit: Option<String>,
368        stop_loss: Option<String>,
369        tp_trigger_by: Option<String>,
370        sl_trigger_by: Option<String>,
371    ) -> Self {
372        Self {
373            category,
374            symbol,
375            order_id,
376            order_link_id,
377            qty,
378            price,
379            trigger_price,
380            take_profit,
381            stop_loss,
382            tp_trigger_by,
383            sl_trigger_by,
384        }
385    }
386}
387
388impl TryFrom<BybitWsAmendOrderParams> for messages::BybitWsAmendOrderParams {
389    type Error = BybitWsError;
390
391    fn try_from(params: BybitWsAmendOrderParams) -> Result<Self, Self::Error> {
392        let tp_trigger_by = params
393            .tp_trigger_by
394            .map(|v| {
395                serde_json::from_str::<BybitTriggerType>(&format!("\"{}\"", v)).map_err(|e| {
396                    BybitWsError::ClientError(format!("Invalid tp_trigger_by '{}': {}", v, e))
397                })
398            })
399            .transpose()?;
400
401        let sl_trigger_by = params
402            .sl_trigger_by
403            .map(|v| {
404                serde_json::from_str::<BybitTriggerType>(&format!("\"{}\"", v)).map_err(|e| {
405                    BybitWsError::ClientError(format!("Invalid sl_trigger_by '{}': {}", v, e))
406                })
407            })
408            .transpose()?;
409
410        Ok(Self {
411            category: params.category,
412            symbol: Ustr::from(&params.symbol),
413            order_id: params.order_id,
414            order_link_id: params.order_link_id,
415            qty: params.qty,
416            price: params.price,
417            trigger_price: params.trigger_price,
418            take_profit: params.take_profit,
419            stop_loss: params.stop_loss,
420            tp_trigger_by,
421            sl_trigger_by,
422        })
423    }
424}
425
426impl From<messages::BybitWsAmendOrderParams> for BybitWsAmendOrderParams {
427    fn from(params: messages::BybitWsAmendOrderParams) -> Self {
428        let tp_trigger_by = params.tp_trigger_by.map(|v| {
429            serde_json::to_string(&v)
430                .expect("Failed to serialize BybitTriggerType")
431                .trim_matches('"')
432                .to_string()
433        });
434        let sl_trigger_by = params.sl_trigger_by.map(|v| {
435            serde_json::to_string(&v)
436                .expect("Failed to serialize BybitTriggerType")
437                .trim_matches('"')
438                .to_string()
439        });
440
441        Self {
442            category: params.category,
443            symbol: params.symbol.to_string(),
444            order_id: params.order_id,
445            order_link_id: params.order_link_id,
446            qty: params.qty,
447            price: params.price,
448            trigger_price: params.trigger_price,
449            take_profit: params.take_profit,
450            stop_loss: params.stop_loss,
451            tp_trigger_by,
452            sl_trigger_by,
453        }
454    }
455}