Skip to main content

nautilus_bybit/python/
params.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
16use pyo3::prelude::*;
17use serde_json;
18use ustr::Ustr;
19
20use crate::{
21    common::enums::{
22        BybitMarketUnit, BybitOrderSide, BybitOrderType, BybitPositionIdx, BybitProductType,
23        BybitTimeInForce, BybitTpSlMode, BybitTriggerType,
24    },
25    websocket::{error::BybitWsError, messages},
26};
27
28/// Parameters for placing an order via WebSocket.
29#[pyclass(from_py_object)]
30#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")]
31#[derive(Clone, Debug)]
32pub struct BybitWsPlaceOrderParams {
33    #[pyo3(get, set)]
34    pub category: BybitProductType,
35    #[pyo3(get, set)]
36    pub symbol: String,
37    #[pyo3(get, set)]
38    pub side: String,
39    #[pyo3(get, set)]
40    pub order_type: String,
41    #[pyo3(get, set)]
42    pub qty: String,
43    #[pyo3(get, set)]
44    pub is_leverage: Option<i32>,
45    #[pyo3(get, set)]
46    pub market_unit: Option<String>,
47    #[pyo3(get, set)]
48    pub price: Option<String>,
49    #[pyo3(get, set)]
50    pub time_in_force: Option<String>,
51    #[pyo3(get, set)]
52    pub order_link_id: Option<String>,
53    #[pyo3(get, set)]
54    pub reduce_only: Option<bool>,
55    #[pyo3(get, set)]
56    pub close_on_trigger: Option<bool>,
57    #[pyo3(get, set)]
58    pub trigger_price: Option<String>,
59    #[pyo3(get, set)]
60    pub trigger_by: Option<String>,
61    #[pyo3(get, set)]
62    pub trigger_direction: Option<i32>,
63    #[pyo3(get, set)]
64    pub tpsl_mode: Option<String>,
65    #[pyo3(get, set)]
66    pub take_profit: Option<String>,
67    #[pyo3(get, set)]
68    pub stop_loss: Option<String>,
69    #[pyo3(get, set)]
70    pub tp_trigger_by: Option<String>,
71    #[pyo3(get, set)]
72    pub sl_trigger_by: Option<String>,
73    #[pyo3(get, set)]
74    pub sl_trigger_price: Option<String>,
75    #[pyo3(get, set)]
76    pub tp_trigger_price: Option<String>,
77    #[pyo3(get, set)]
78    pub sl_order_type: Option<String>,
79    #[pyo3(get, set)]
80    pub tp_order_type: Option<String>,
81    #[pyo3(get, set)]
82    pub sl_limit_price: Option<String>,
83    #[pyo3(get, set)]
84    pub tp_limit_price: Option<String>,
85    #[pyo3(get, set)]
86    pub order_iv: Option<String>,
87    #[pyo3(get, set)]
88    pub mmp: Option<bool>,
89    #[pyo3(get, set)]
90    pub position_idx: Option<BybitPositionIdx>,
91}
92
93#[pymethods]
94#[pyo3_stub_gen::derive::gen_stub_pymethods]
95impl BybitWsPlaceOrderParams {
96    /// Parameters for placing an order via WebSocket.
97    #[new]
98    #[pyo3(signature = (
99        category,
100        symbol,
101        side,
102        order_type,
103        qty,
104        is_leverage=None,
105        market_unit=None,
106        price=None,
107        time_in_force=None,
108        order_link_id=None,
109        reduce_only=None,
110        close_on_trigger=None,
111        trigger_price=None,
112        trigger_by=None,
113        trigger_direction=None,
114        tpsl_mode=None,
115        take_profit=None,
116        stop_loss=None,
117        tp_trigger_by=None,
118        sl_trigger_by=None,
119        sl_trigger_price=None,
120        tp_trigger_price=None,
121        sl_order_type=None,
122        tp_order_type=None,
123        sl_limit_price=None,
124        tp_limit_price=None,
125        order_iv=None,
126        mmp=None,
127        position_idx=None,
128    ))]
129    #[expect(clippy::too_many_arguments)]
130    fn py_new(
131        category: BybitProductType,
132        symbol: String,
133        side: String,
134        order_type: String,
135        qty: String,
136        is_leverage: Option<i32>,
137        market_unit: Option<String>,
138        price: Option<String>,
139        time_in_force: Option<String>,
140        order_link_id: Option<String>,
141        reduce_only: Option<bool>,
142        close_on_trigger: Option<bool>,
143        trigger_price: Option<String>,
144        trigger_by: Option<String>,
145        trigger_direction: Option<i32>,
146        tpsl_mode: Option<String>,
147        take_profit: Option<String>,
148        stop_loss: Option<String>,
149        tp_trigger_by: Option<String>,
150        sl_trigger_by: Option<String>,
151        sl_trigger_price: Option<String>,
152        tp_trigger_price: Option<String>,
153        sl_order_type: Option<String>,
154        tp_order_type: Option<String>,
155        sl_limit_price: Option<String>,
156        tp_limit_price: Option<String>,
157        order_iv: Option<String>,
158        mmp: Option<bool>,
159        position_idx: Option<BybitPositionIdx>,
160    ) -> Self {
161        Self {
162            category,
163            symbol,
164            side,
165            order_type,
166            qty,
167            is_leverage,
168            market_unit,
169            price,
170            time_in_force,
171            order_link_id,
172            reduce_only,
173            close_on_trigger,
174            trigger_price,
175            trigger_by,
176            trigger_direction,
177            tpsl_mode,
178            take_profit,
179            stop_loss,
180            tp_trigger_by,
181            sl_trigger_by,
182            sl_trigger_price,
183            tp_trigger_price,
184            sl_order_type,
185            tp_order_type,
186            sl_limit_price,
187            tp_limit_price,
188            order_iv,
189            mmp,
190            position_idx,
191        }
192    }
193}
194
195impl TryFrom<BybitWsPlaceOrderParams> for messages::BybitWsPlaceOrderParams {
196    type Error = BybitWsError;
197
198    fn try_from(params: BybitWsPlaceOrderParams) -> Result<Self, Self::Error> {
199        let side: BybitOrderSide =
200            serde_json::from_str(&format!("\"{}\"", params.side)).map_err(|e| {
201                BybitWsError::ClientError(format!("Invalid side '{}': {}", params.side, e))
202            })?;
203        let order_type: BybitOrderType =
204            serde_json::from_str(&format!("\"{}\"", params.order_type)).map_err(|e| {
205                BybitWsError::ClientError(format!(
206                    "Invalid order_type '{}': {}",
207                    params.order_type, e
208                ))
209            })?;
210
211        let time_in_force = params
212            .time_in_force
213            .map(|v| {
214                serde_json::from_str::<BybitTimeInForce>(&format!("\"{v}\"")).map_err(|e| {
215                    BybitWsError::ClientError(format!("Invalid time_in_force '{v}': {e}"))
216                })
217            })
218            .transpose()?;
219
220        let trigger_by = params
221            .trigger_by
222            .map(|v| {
223                serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
224                    BybitWsError::ClientError(format!("Invalid trigger_by '{v}': {e}"))
225                })
226            })
227            .transpose()?;
228
229        let tp_trigger_by = params
230            .tp_trigger_by
231            .map(|v| {
232                serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
233                    BybitWsError::ClientError(format!("Invalid tp_trigger_by '{v}': {e}"))
234                })
235            })
236            .transpose()?;
237
238        let sl_trigger_by = params
239            .sl_trigger_by
240            .map(|v| {
241                serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
242                    BybitWsError::ClientError(format!("Invalid sl_trigger_by '{v}': {e}"))
243                })
244            })
245            .transpose()?;
246
247        let sl_order_type = params
248            .sl_order_type
249            .map(|v| {
250                serde_json::from_str::<BybitOrderType>(&format!("\"{v}\"")).map_err(|e| {
251                    BybitWsError::ClientError(format!("Invalid sl_order_type '{v}': {e}"))
252                })
253            })
254            .transpose()?;
255
256        let tp_order_type = params
257            .tp_order_type
258            .map(|v| {
259                serde_json::from_str::<BybitOrderType>(&format!("\"{v}\"")).map_err(|e| {
260                    BybitWsError::ClientError(format!("Invalid tp_order_type '{v}': {e}"))
261                })
262            })
263            .transpose()?;
264
265        let tpsl_mode = params
266            .tpsl_mode
267            .map(|v| {
268                serde_json::from_str::<BybitTpSlMode>(&format!("\"{v}\""))
269                    .map_err(|e| BybitWsError::ClientError(format!("Invalid tpsl_mode '{v}': {e}")))
270            })
271            .transpose()?;
272
273        let market_unit = params
274            .market_unit
275            .map(|v| {
276                serde_json::from_str::<BybitMarketUnit>(&format!("\"{v}\"")).map_err(|e| {
277                    BybitWsError::ClientError(format!("Invalid market_unit '{v}': {e}"))
278                })
279            })
280            .transpose()?;
281
282        Ok(Self {
283            category: params.category,
284            symbol: Ustr::from(&params.symbol),
285            side,
286            order_type,
287            qty: params.qty,
288            is_leverage: params.is_leverage,
289            market_unit,
290            price: params.price,
291            time_in_force,
292            order_link_id: params.order_link_id,
293            reduce_only: params.reduce_only,
294            close_on_trigger: params.close_on_trigger,
295            trigger_price: params.trigger_price,
296            trigger_by,
297            trigger_direction: params.trigger_direction,
298            tpsl_mode,
299            take_profit: params.take_profit,
300            stop_loss: params.stop_loss,
301            tp_trigger_by,
302            sl_trigger_by,
303            sl_trigger_price: params.sl_trigger_price,
304            tp_trigger_price: params.tp_trigger_price,
305            sl_order_type,
306            tp_order_type,
307            sl_limit_price: params.sl_limit_price,
308            tp_limit_price: params.tp_limit_price,
309            order_iv: params.order_iv,
310            mmp: params.mmp,
311            position_idx: params.position_idx,
312        })
313    }
314}
315
316impl From<messages::BybitWsPlaceOrderParams> for BybitWsPlaceOrderParams {
317    fn from(params: messages::BybitWsPlaceOrderParams) -> Self {
318        let side = serde_json::to_string(&params.side)
319            .expect("Failed to serialize BybitOrderSide")
320            .trim_matches('"')
321            .to_string();
322        let order_type = serde_json::to_string(&params.order_type)
323            .expect("Failed to serialize BybitOrderType")
324            .trim_matches('"')
325            .to_string();
326        let time_in_force = params.time_in_force.map(|v| {
327            serde_json::to_string(&v)
328                .expect("Failed to serialize BybitTimeInForce")
329                .trim_matches('"')
330                .to_string()
331        });
332        let trigger_by = params.trigger_by.map(|v| {
333            serde_json::to_string(&v)
334                .expect("Failed to serialize BybitTriggerType")
335                .trim_matches('"')
336                .to_string()
337        });
338        let tp_trigger_by = params.tp_trigger_by.map(|v| {
339            serde_json::to_string(&v)
340                .expect("Failed to serialize BybitTriggerType")
341                .trim_matches('"')
342                .to_string()
343        });
344        let sl_trigger_by = params.sl_trigger_by.map(|v| {
345            serde_json::to_string(&v)
346                .expect("Failed to serialize BybitTriggerType")
347                .trim_matches('"')
348                .to_string()
349        });
350        let sl_order_type = params.sl_order_type.map(|v| {
351            serde_json::to_string(&v)
352                .expect("Failed to serialize BybitOrderType")
353                .trim_matches('"')
354                .to_string()
355        });
356        let tp_order_type = params.tp_order_type.map(|v| {
357            serde_json::to_string(&v)
358                .expect("Failed to serialize BybitOrderType")
359                .trim_matches('"')
360                .to_string()
361        });
362
363        let tpsl_mode = params.tpsl_mode.map(|v| {
364            serde_json::to_string(&v)
365                .expect("Failed to serialize BybitTpSlMode")
366                .trim_matches('"')
367                .to_string()
368        });
369        let market_unit = params.market_unit.map(|v| {
370            serde_json::to_string(&v)
371                .expect("Failed to serialize BybitMarketUnit")
372                .trim_matches('"')
373                .to_string()
374        });
375
376        Self {
377            category: params.category,
378            symbol: params.symbol.to_string(),
379            side,
380            order_type,
381            qty: params.qty,
382            is_leverage: params.is_leverage,
383            market_unit,
384            price: params.price,
385            time_in_force,
386            order_link_id: params.order_link_id,
387            reduce_only: params.reduce_only,
388            close_on_trigger: params.close_on_trigger,
389            trigger_price: params.trigger_price,
390            trigger_by,
391            trigger_direction: params.trigger_direction,
392            tpsl_mode,
393            take_profit: params.take_profit,
394            stop_loss: params.stop_loss,
395            tp_trigger_by,
396            sl_trigger_by,
397            sl_trigger_price: params.sl_trigger_price,
398            tp_trigger_price: params.tp_trigger_price,
399            sl_order_type,
400            tp_order_type,
401            sl_limit_price: params.sl_limit_price,
402            tp_limit_price: params.tp_limit_price,
403            order_iv: params.order_iv,
404            mmp: params.mmp,
405            position_idx: params.position_idx,
406        }
407    }
408}
409
410/// Parameters for amending an order via WebSocket.
411#[pyclass(from_py_object)]
412#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")]
413#[derive(Clone, Debug)]
414pub struct BybitWsAmendOrderParams {
415    #[pyo3(get, set)]
416    pub category: BybitProductType,
417    #[pyo3(get, set)]
418    pub symbol: String,
419    #[pyo3(get, set)]
420    pub order_id: Option<String>,
421    #[pyo3(get, set)]
422    pub order_link_id: Option<String>,
423    #[pyo3(get, set)]
424    pub qty: Option<String>,
425    #[pyo3(get, set)]
426    pub price: Option<String>,
427    #[pyo3(get, set)]
428    pub trigger_price: Option<String>,
429    #[pyo3(get, set)]
430    pub take_profit: Option<String>,
431    #[pyo3(get, set)]
432    pub stop_loss: Option<String>,
433    #[pyo3(get, set)]
434    pub tp_trigger_by: Option<String>,
435    #[pyo3(get, set)]
436    pub sl_trigger_by: Option<String>,
437    #[pyo3(get, set)]
438    pub order_iv: Option<String>,
439}
440
441#[pymethods]
442#[pyo3_stub_gen::derive::gen_stub_pymethods]
443impl BybitWsAmendOrderParams {
444    /// Parameters for amending an order via WebSocket.
445    #[new]
446    #[expect(clippy::too_many_arguments)]
447    fn py_new(
448        category: BybitProductType,
449        symbol: String,
450        order_id: Option<String>,
451        order_link_id: Option<String>,
452        qty: Option<String>,
453        price: Option<String>,
454        trigger_price: Option<String>,
455        take_profit: Option<String>,
456        stop_loss: Option<String>,
457        tp_trigger_by: Option<String>,
458        sl_trigger_by: Option<String>,
459        order_iv: Option<String>,
460    ) -> Self {
461        Self {
462            category,
463            symbol,
464            order_id,
465            order_link_id,
466            qty,
467            price,
468            trigger_price,
469            take_profit,
470            stop_loss,
471            tp_trigger_by,
472            sl_trigger_by,
473            order_iv,
474        }
475    }
476}
477
478impl TryFrom<BybitWsAmendOrderParams> for messages::BybitWsAmendOrderParams {
479    type Error = BybitWsError;
480
481    fn try_from(params: BybitWsAmendOrderParams) -> Result<Self, Self::Error> {
482        let tp_trigger_by = params
483            .tp_trigger_by
484            .map(|v| {
485                serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
486                    BybitWsError::ClientError(format!("Invalid tp_trigger_by '{v}': {e}"))
487                })
488            })
489            .transpose()?;
490
491        let sl_trigger_by = params
492            .sl_trigger_by
493            .map(|v| {
494                serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
495                    BybitWsError::ClientError(format!("Invalid sl_trigger_by '{v}': {e}"))
496                })
497            })
498            .transpose()?;
499
500        Ok(Self {
501            category: params.category,
502            symbol: Ustr::from(&params.symbol),
503            order_id: params.order_id,
504            order_link_id: params.order_link_id,
505            qty: params.qty,
506            price: params.price,
507            trigger_price: params.trigger_price,
508            take_profit: params.take_profit,
509            stop_loss: params.stop_loss,
510            tp_trigger_by,
511            sl_trigger_by,
512            order_iv: params.order_iv,
513        })
514    }
515}
516
517impl From<messages::BybitWsAmendOrderParams> for BybitWsAmendOrderParams {
518    fn from(params: messages::BybitWsAmendOrderParams) -> Self {
519        let tp_trigger_by = params.tp_trigger_by.map(|v| {
520            serde_json::to_string(&v)
521                .expect("Failed to serialize BybitTriggerType")
522                .trim_matches('"')
523                .to_string()
524        });
525        let sl_trigger_by = params.sl_trigger_by.map(|v| {
526            serde_json::to_string(&v)
527                .expect("Failed to serialize BybitTriggerType")
528                .trim_matches('"')
529                .to_string()
530        });
531
532        Self {
533            category: params.category,
534            symbol: params.symbol.to_string(),
535            order_id: params.order_id,
536            order_link_id: params.order_link_id,
537            qty: params.qty,
538            price: params.price,
539            trigger_price: params.trigger_price,
540            take_profit: params.take_profit,
541            stop_loss: params.stop_loss,
542            tp_trigger_by,
543            sl_trigger_by,
544            order_iv: params.order_iv,
545        }
546    }
547}
548
549/// Parameters for canceling an order via WebSocket.
550#[pyclass(from_py_object)]
551#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")]
552#[derive(Clone, Debug)]
553pub struct BybitWsCancelOrderParams {
554    #[pyo3(get, set)]
555    pub category: BybitProductType,
556    #[pyo3(get, set)]
557    pub symbol: String,
558    #[pyo3(get, set)]
559    pub order_id: Option<String>,
560    #[pyo3(get, set)]
561    pub order_link_id: Option<String>,
562}
563
564#[pymethods]
565#[pyo3_stub_gen::derive::gen_stub_pymethods]
566impl BybitWsCancelOrderParams {
567    /// Parameters for canceling an order via WebSocket.
568    #[new]
569    fn py_new(
570        category: BybitProductType,
571        symbol: String,
572        order_id: Option<String>,
573        order_link_id: Option<String>,
574    ) -> Self {
575        Self {
576            category,
577            symbol,
578            order_id,
579            order_link_id,
580        }
581    }
582}
583
584impl TryFrom<BybitWsCancelOrderParams> for messages::BybitWsCancelOrderParams {
585    type Error = BybitWsError;
586
587    fn try_from(params: BybitWsCancelOrderParams) -> Result<Self, Self::Error> {
588        Ok(Self {
589            category: params.category,
590            symbol: Ustr::from(&params.symbol),
591            order_id: params.order_id,
592            order_link_id: params.order_link_id,
593        })
594    }
595}
596
597impl From<messages::BybitWsCancelOrderParams> for BybitWsCancelOrderParams {
598    fn from(params: messages::BybitWsCancelOrderParams) -> Self {
599        Self {
600            category: params.category,
601            symbol: params.symbol.to_string(),
602            order_id: params.order_id,
603            order_link_id: params.order_link_id,
604        }
605    }
606}
607
608/// Parameters for fetching tickers via HTTP API.
609#[pyclass(from_py_object)]
610#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")]
611#[derive(Clone, Debug)]
612pub struct BybitTickersParams {
613    #[pyo3(get, set)]
614    pub category: BybitProductType,
615    #[pyo3(get, set)]
616    pub symbol: Option<String>,
617    #[pyo3(get, set)]
618    pub base_coin: Option<String>,
619    #[pyo3(get, set)]
620    pub exp_date: Option<String>,
621}
622
623#[pymethods]
624#[pyo3_stub_gen::derive::gen_stub_pymethods]
625impl BybitTickersParams {
626    /// Query parameters for `GET /v5/market/tickers`.
627    ///
628    /// # References
629    /// - <https://bybit-exchange.github.io/docs/v5/market/tickers>
630    #[new]
631    #[pyo3(signature = (category, symbol=None, base_coin=None, exp_date=None))]
632    fn py_new(
633        category: BybitProductType,
634        symbol: Option<String>,
635        base_coin: Option<String>,
636        exp_date: Option<String>,
637    ) -> Self {
638        Self {
639            category,
640            symbol,
641            base_coin,
642            exp_date,
643        }
644    }
645}
646
647impl From<BybitTickersParams> for crate::http::query::BybitTickersParams {
648    fn from(params: BybitTickersParams) -> Self {
649        Self {
650            category: params.category,
651            symbol: params.symbol,
652            base_coin: params.base_coin,
653            exp_date: params.exp_date,
654        }
655    }
656}