1use pyo3::prelude::*;
17use serde_json;
18use ustr::Ustr;
19
20use crate::{
21 common::{
22 enums::{
23 BybitBboSideType, BybitMarketUnit, BybitOrderSide, BybitOrderType, BybitPositionIdx,
24 BybitProductType, BybitTimeInForce, BybitTriggerType,
25 },
26 parse::{parse_bbo_level, parse_tp_sl_order_type, parse_tpsl_mode, parse_trigger_type},
27 },
28 http::query::BybitNativeTpSlParams as RustNativeTpSlParams,
29 websocket::{error::BybitWsError, messages},
30};
31
32#[pyclass(from_py_object)]
34#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")]
35#[derive(Clone, Debug)]
36pub struct BybitWsPlaceOrderParams {
37 #[pyo3(get, set)]
38 pub category: BybitProductType,
39 #[pyo3(get, set)]
40 pub symbol: String,
41 #[pyo3(get, set)]
42 pub side: String,
43 #[pyo3(get, set)]
44 pub order_type: String,
45 #[pyo3(get, set)]
46 pub qty: String,
47 #[pyo3(get, set)]
48 pub is_leverage: Option<i32>,
49 #[pyo3(get, set)]
50 pub market_unit: Option<String>,
51 #[pyo3(get, set)]
52 pub price: Option<String>,
53 #[pyo3(get, set)]
54 pub time_in_force: Option<String>,
55 #[pyo3(get, set)]
56 pub order_link_id: Option<String>,
57 #[pyo3(get, set)]
58 pub reduce_only: Option<bool>,
59 #[pyo3(get, set)]
60 pub close_on_trigger: Option<bool>,
61 #[pyo3(get, set)]
62 pub trigger_price: Option<String>,
63 #[pyo3(get, set)]
64 pub trigger_by: Option<String>,
65 #[pyo3(get, set)]
66 pub trigger_direction: Option<i32>,
67 #[pyo3(get, set)]
68 pub tpsl_mode: Option<String>,
69 #[pyo3(get, set)]
70 pub take_profit: Option<String>,
71 #[pyo3(get, set)]
72 pub stop_loss: Option<String>,
73 #[pyo3(get, set)]
74 pub tp_trigger_by: Option<String>,
75 #[pyo3(get, set)]
76 pub sl_trigger_by: Option<String>,
77 #[pyo3(get, set)]
78 pub sl_trigger_price: Option<String>,
79 #[pyo3(get, set)]
80 pub tp_trigger_price: Option<String>,
81 #[pyo3(get, set)]
82 pub sl_order_type: Option<String>,
83 #[pyo3(get, set)]
84 pub tp_order_type: Option<String>,
85 #[pyo3(get, set)]
86 pub sl_limit_price: Option<String>,
87 #[pyo3(get, set)]
88 pub tp_limit_price: Option<String>,
89 #[pyo3(get, set)]
90 pub order_iv: Option<String>,
91 #[pyo3(get, set)]
92 pub mmp: Option<bool>,
93 #[pyo3(get, set)]
94 pub position_idx: Option<BybitPositionIdx>,
95 #[pyo3(get, set)]
96 pub bbo_side_type: Option<String>,
97 #[pyo3(get, set)]
98 pub bbo_level: Option<String>,
99}
100
101#[pymethods]
102#[pyo3_stub_gen::derive::gen_stub_pymethods]
103impl BybitWsPlaceOrderParams {
104 #[new]
106 #[pyo3(signature = (
107 category,
108 symbol,
109 side,
110 order_type,
111 qty,
112 is_leverage=None,
113 market_unit=None,
114 price=None,
115 time_in_force=None,
116 order_link_id=None,
117 reduce_only=None,
118 close_on_trigger=None,
119 trigger_price=None,
120 trigger_by=None,
121 trigger_direction=None,
122 tpsl_mode=None,
123 take_profit=None,
124 stop_loss=None,
125 tp_trigger_by=None,
126 sl_trigger_by=None,
127 sl_trigger_price=None,
128 tp_trigger_price=None,
129 sl_order_type=None,
130 tp_order_type=None,
131 sl_limit_price=None,
132 tp_limit_price=None,
133 order_iv=None,
134 mmp=None,
135 position_idx=None,
136 bbo_side_type=None,
137 bbo_level=None,
138 ))]
139 #[expect(clippy::too_many_arguments)]
140 fn py_new(
141 category: BybitProductType,
142 symbol: String,
143 side: String,
144 order_type: String,
145 qty: String,
146 is_leverage: Option<i32>,
147 market_unit: Option<String>,
148 price: Option<String>,
149 time_in_force: Option<String>,
150 order_link_id: Option<String>,
151 reduce_only: Option<bool>,
152 close_on_trigger: Option<bool>,
153 trigger_price: Option<String>,
154 trigger_by: Option<String>,
155 trigger_direction: Option<i32>,
156 tpsl_mode: Option<String>,
157 take_profit: Option<String>,
158 stop_loss: Option<String>,
159 tp_trigger_by: Option<String>,
160 sl_trigger_by: Option<String>,
161 sl_trigger_price: Option<String>,
162 tp_trigger_price: Option<String>,
163 sl_order_type: Option<String>,
164 tp_order_type: Option<String>,
165 sl_limit_price: Option<String>,
166 tp_limit_price: Option<String>,
167 order_iv: Option<String>,
168 mmp: Option<bool>,
169 position_idx: Option<BybitPositionIdx>,
170 bbo_side_type: Option<String>,
171 bbo_level: Option<String>,
172 ) -> Self {
173 Self {
174 category,
175 symbol,
176 side,
177 order_type,
178 qty,
179 is_leverage,
180 market_unit,
181 price,
182 time_in_force,
183 order_link_id,
184 reduce_only,
185 close_on_trigger,
186 trigger_price,
187 trigger_by,
188 trigger_direction,
189 tpsl_mode,
190 take_profit,
191 stop_loss,
192 tp_trigger_by,
193 sl_trigger_by,
194 sl_trigger_price,
195 tp_trigger_price,
196 sl_order_type,
197 tp_order_type,
198 sl_limit_price,
199 tp_limit_price,
200 order_iv,
201 mmp,
202 position_idx,
203 bbo_side_type,
204 bbo_level,
205 }
206 }
207}
208
209impl TryFrom<BybitWsPlaceOrderParams> for messages::BybitWsPlaceOrderParams {
210 type Error = BybitWsError;
211
212 fn try_from(params: BybitWsPlaceOrderParams) -> Result<Self, Self::Error> {
213 let side: BybitOrderSide =
214 serde_json::from_str(&format!("\"{}\"", params.side)).map_err(|e| {
215 BybitWsError::ClientError(format!("Invalid side '{}': {}", params.side, e))
216 })?;
217 let order_type: BybitOrderType =
218 serde_json::from_str(&format!("\"{}\"", params.order_type)).map_err(|e| {
219 BybitWsError::ClientError(format!(
220 "Invalid order_type '{}': {}",
221 params.order_type, e
222 ))
223 })?;
224
225 let time_in_force = params
226 .time_in_force
227 .map(|v| {
228 serde_json::from_str::<BybitTimeInForce>(&format!("\"{v}\"")).map_err(|e| {
229 BybitWsError::ClientError(format!("Invalid time_in_force '{v}': {e}"))
230 })
231 })
232 .transpose()?;
233
234 let trigger_by = params
235 .trigger_by
236 .map(|v| {
237 serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
238 BybitWsError::ClientError(format!("Invalid trigger_by '{v}': {e}"))
239 })
240 })
241 .transpose()?;
242
243 let tp_trigger_by = params
244 .tp_trigger_by
245 .map(|v| {
246 serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
247 BybitWsError::ClientError(format!("Invalid tp_trigger_by '{v}': {e}"))
248 })
249 })
250 .transpose()?;
251
252 let sl_trigger_by = params
253 .sl_trigger_by
254 .map(|v| {
255 serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
256 BybitWsError::ClientError(format!("Invalid sl_trigger_by '{v}': {e}"))
257 })
258 })
259 .transpose()?;
260
261 let sl_order_type = params
262 .sl_order_type
263 .map(|v| {
264 serde_json::from_str::<BybitOrderType>(&format!("\"{v}\"")).map_err(|e| {
265 BybitWsError::ClientError(format!("Invalid sl_order_type '{v}': {e}"))
266 })
267 })
268 .transpose()?;
269
270 let tp_order_type = params
271 .tp_order_type
272 .map(|v| {
273 serde_json::from_str::<BybitOrderType>(&format!("\"{v}\"")).map_err(|e| {
274 BybitWsError::ClientError(format!("Invalid tp_order_type '{v}': {e}"))
275 })
276 })
277 .transpose()?;
278
279 let tpsl_mode = params
280 .tpsl_mode
281 .map(|v| {
282 parse_tpsl_mode(&v)
283 .map_err(|e| BybitWsError::ClientError(format!("Invalid tpsl_mode '{v}': {e}")))
284 })
285 .transpose()?;
286
287 let market_unit = params
288 .market_unit
289 .map(|v| {
290 serde_json::from_str::<BybitMarketUnit>(&format!("\"{v}\"")).map_err(|e| {
291 BybitWsError::ClientError(format!("Invalid market_unit '{v}': {e}"))
292 })
293 })
294 .transpose()?;
295
296 let bbo_side_type = params
297 .bbo_side_type
298 .map(|v| {
299 serde_json::from_str::<BybitBboSideType>(&format!("\"{v}\"")).map_err(|e| {
300 BybitWsError::ClientError(format!("Invalid bbo_side_type '{v}': {e}"))
301 })
302 })
303 .transpose()?;
304 let bbo_level = params
305 .bbo_level
306 .map(parse_bbo_level)
307 .transpose()
308 .map_err(|e| BybitWsError::ClientError(e.to_string()))?;
309
310 if bbo_side_type.is_some() != bbo_level.is_some() {
311 return Err(BybitWsError::ClientError(
312 "'bbo_side_type' and 'bbo_level' must be provided together".to_string(),
313 ));
314 }
315
316 Ok(Self {
317 category: params.category,
318 symbol: Ustr::from(¶ms.symbol),
319 side,
320 order_type,
321 qty: params.qty,
322 is_leverage: params.is_leverage,
323 market_unit,
324 price: params.price,
325 time_in_force,
326 order_link_id: params.order_link_id,
327 reduce_only: params.reduce_only,
328 close_on_trigger: params.close_on_trigger,
329 trigger_price: params.trigger_price,
330 trigger_by,
331 trigger_direction: params.trigger_direction,
332 tpsl_mode,
333 take_profit: params.take_profit,
334 stop_loss: params.stop_loss,
335 tp_trigger_by,
336 sl_trigger_by,
337 sl_trigger_price: params.sl_trigger_price,
338 tp_trigger_price: params.tp_trigger_price,
339 sl_order_type,
340 tp_order_type,
341 sl_limit_price: params.sl_limit_price,
342 tp_limit_price: params.tp_limit_price,
343 order_iv: params.order_iv,
344 mmp: params.mmp,
345 position_idx: params.position_idx,
346 bbo_side_type,
347 bbo_level,
348 })
349 }
350}
351
352impl From<messages::BybitWsPlaceOrderParams> for BybitWsPlaceOrderParams {
353 fn from(params: messages::BybitWsPlaceOrderParams) -> Self {
354 let side = serde_json::to_string(¶ms.side)
355 .expect("Failed to serialize BybitOrderSide")
356 .trim_matches('"')
357 .to_string();
358 let order_type = serde_json::to_string(¶ms.order_type)
359 .expect("Failed to serialize BybitOrderType")
360 .trim_matches('"')
361 .to_string();
362 let time_in_force = params.time_in_force.map(|v| {
363 serde_json::to_string(&v)
364 .expect("Failed to serialize BybitTimeInForce")
365 .trim_matches('"')
366 .to_string()
367 });
368 let trigger_by = params.trigger_by.map(|v| {
369 serde_json::to_string(&v)
370 .expect("Failed to serialize BybitTriggerType")
371 .trim_matches('"')
372 .to_string()
373 });
374 let tp_trigger_by = params.tp_trigger_by.map(|v| {
375 serde_json::to_string(&v)
376 .expect("Failed to serialize BybitTriggerType")
377 .trim_matches('"')
378 .to_string()
379 });
380 let sl_trigger_by = params.sl_trigger_by.map(|v| {
381 serde_json::to_string(&v)
382 .expect("Failed to serialize BybitTriggerType")
383 .trim_matches('"')
384 .to_string()
385 });
386 let sl_order_type = params.sl_order_type.map(|v| {
387 serde_json::to_string(&v)
388 .expect("Failed to serialize BybitOrderType")
389 .trim_matches('"')
390 .to_string()
391 });
392 let tp_order_type = params.tp_order_type.map(|v| {
393 serde_json::to_string(&v)
394 .expect("Failed to serialize BybitOrderType")
395 .trim_matches('"')
396 .to_string()
397 });
398
399 let tpsl_mode = params.tpsl_mode.map(|v| {
400 serde_json::to_string(&v)
401 .expect("Failed to serialize BybitTpSlMode")
402 .trim_matches('"')
403 .to_string()
404 });
405 let market_unit = params.market_unit.map(|v| {
406 serde_json::to_string(&v)
407 .expect("Failed to serialize BybitMarketUnit")
408 .trim_matches('"')
409 .to_string()
410 });
411 let bbo_side_type = params.bbo_side_type.map(|v| {
412 serde_json::to_string(&v)
413 .expect("Failed to serialize BybitBboSideType")
414 .trim_matches('"')
415 .to_string()
416 });
417
418 Self {
419 category: params.category,
420 symbol: params.symbol.to_string(),
421 side,
422 order_type,
423 qty: params.qty,
424 is_leverage: params.is_leverage,
425 market_unit,
426 price: params.price,
427 time_in_force,
428 order_link_id: params.order_link_id,
429 reduce_only: params.reduce_only,
430 close_on_trigger: params.close_on_trigger,
431 trigger_price: params.trigger_price,
432 trigger_by,
433 trigger_direction: params.trigger_direction,
434 tpsl_mode,
435 take_profit: params.take_profit,
436 stop_loss: params.stop_loss,
437 tp_trigger_by,
438 sl_trigger_by,
439 sl_trigger_price: params.sl_trigger_price,
440 tp_trigger_price: params.tp_trigger_price,
441 sl_order_type,
442 tp_order_type,
443 sl_limit_price: params.sl_limit_price,
444 tp_limit_price: params.tp_limit_price,
445 order_iv: params.order_iv,
446 mmp: params.mmp,
447 position_idx: params.position_idx,
448 bbo_side_type,
449 bbo_level: params.bbo_level,
450 }
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use rstest::rstest;
457
458 use super::*;
459 use crate::common::enums::BybitTpSlMode;
460
461 fn place_order_params(
462 bbo_side_type: Option<&str>,
463 bbo_level: Option<&str>,
464 ) -> BybitWsPlaceOrderParams {
465 BybitWsPlaceOrderParams {
466 category: BybitProductType::Linear,
467 symbol: "BTCUSDT".to_string(),
468 side: "Buy".to_string(),
469 order_type: "Limit".to_string(),
470 qty: "0.001".to_string(),
471 is_leverage: None,
472 market_unit: None,
473 price: None,
474 time_in_force: Some("GTC".to_string()),
475 order_link_id: Some("test-bbo-1".to_string()),
476 reduce_only: None,
477 close_on_trigger: None,
478 trigger_price: None,
479 trigger_by: None,
480 trigger_direction: None,
481 tpsl_mode: None,
482 take_profit: None,
483 stop_loss: None,
484 tp_trigger_by: None,
485 sl_trigger_by: None,
486 sl_trigger_price: None,
487 tp_trigger_price: None,
488 sl_order_type: None,
489 tp_order_type: None,
490 sl_limit_price: None,
491 tp_limit_price: None,
492 order_iv: None,
493 mmp: None,
494 position_idx: None,
495 bbo_side_type: bbo_side_type.map(str::to_string),
496 bbo_level: bbo_level.map(str::to_string),
497 }
498 }
499
500 #[rstest]
501 fn test_place_order_params_try_from_accepts_bbo_pair() {
502 let params = place_order_params(Some("Queue"), Some("2"));
503 let result = messages::BybitWsPlaceOrderParams::try_from(params).unwrap();
504
505 assert_eq!(result.bbo_side_type, Some(BybitBboSideType::Queue));
506 assert_eq!(result.bbo_level.as_deref(), Some("2"));
507 }
508
509 #[rstest]
510 fn test_place_order_params_try_from_rejects_unpaired_bbo() {
511 let params = place_order_params(Some("Queue"), None);
512 let err = messages::BybitWsPlaceOrderParams::try_from(params).unwrap_err();
513
514 assert!(
515 err.to_string()
516 .contains("'bbo_side_type' and 'bbo_level' must be provided together")
517 );
518 }
519
520 #[rstest]
521 fn test_place_order_params_try_from_rejects_invalid_bbo_level() {
522 let params = place_order_params(Some("Queue"), Some("6"));
523 let err = messages::BybitWsPlaceOrderParams::try_from(params).unwrap_err();
524
525 assert!(err.to_string().contains("invalid 'bbo_level'"));
526 }
527
528 #[rstest]
529 fn test_native_tp_sl_params_try_from_accepts_valid_enums() {
530 let params = BybitNativeTpSlParams {
531 take_profit: Some("55000".to_string()),
532 stop_loss: Some("47000".to_string()),
533 tp_trigger_by: Some("LastPrice".to_string()),
534 sl_trigger_by: Some("MarkPrice".to_string()),
535 tp_order_type: Some("Limit".to_string()),
536 sl_order_type: Some("Market".to_string()),
537 tpsl_mode: Some("Partial".to_string()),
538 ..Default::default()
539 };
540
541 let native = RustNativeTpSlParams::try_from(params).unwrap();
542
543 assert_eq!(native.tp_trigger_by, Some(BybitTriggerType::LastPrice));
544 assert_eq!(native.sl_trigger_by, Some(BybitTriggerType::MarkPrice));
545 assert_eq!(native.tp_order_type, Some(BybitOrderType::Limit));
546 assert_eq!(native.sl_order_type, Some(BybitOrderType::Market));
547 assert_eq!(native.tpsl_mode, Some(BybitTpSlMode::Partial));
548 }
549
550 #[rstest]
551 #[case("tp_trigger_by")]
552 #[case("sl_trigger_by")]
553 #[case("tp_order_type")]
554 #[case("sl_order_type")]
555 #[case("tpsl_mode")]
556 fn test_native_tp_sl_params_try_from_rejects_invalid_enum(#[case] field: &str) {
557 let mut params = BybitNativeTpSlParams::default();
558 match field {
559 "tp_trigger_by" => params.tp_trigger_by = Some("garbage".to_string()),
560 "sl_trigger_by" => params.sl_trigger_by = Some("garbage".to_string()),
561 "tp_order_type" => params.tp_order_type = Some("garbage".to_string()),
562 "sl_order_type" => params.sl_order_type = Some("garbage".to_string()),
563 "tpsl_mode" => params.tpsl_mode = Some("garbage".to_string()),
564 _ => unreachable!(),
565 }
566
567 let err = RustNativeTpSlParams::try_from(params).unwrap_err();
568 assert!(err.to_string().contains("garbage"));
569 }
570
571 #[rstest]
572 fn test_native_tp_sl_params_try_from_rejects_unknown_tpsl_mode() {
573 let params = BybitNativeTpSlParams {
576 tpsl_mode: Some("Unknown".to_string()),
577 ..Default::default()
578 };
579
580 let err = RustNativeTpSlParams::try_from(params).unwrap_err();
581 assert!(err.to_string().contains("invalid Bybit TP/SL mode"));
582 }
583}
584
585#[pyclass(from_py_object)]
587#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")]
588#[derive(Clone, Debug)]
589pub struct BybitWsAmendOrderParams {
590 #[pyo3(get, set)]
591 pub category: BybitProductType,
592 #[pyo3(get, set)]
593 pub symbol: String,
594 #[pyo3(get, set)]
595 pub order_id: Option<String>,
596 #[pyo3(get, set)]
597 pub order_link_id: Option<String>,
598 #[pyo3(get, set)]
599 pub qty: Option<String>,
600 #[pyo3(get, set)]
601 pub price: Option<String>,
602 #[pyo3(get, set)]
603 pub trigger_price: Option<String>,
604 #[pyo3(get, set)]
605 pub take_profit: Option<String>,
606 #[pyo3(get, set)]
607 pub stop_loss: Option<String>,
608 #[pyo3(get, set)]
609 pub tp_trigger_by: Option<String>,
610 #[pyo3(get, set)]
611 pub sl_trigger_by: Option<String>,
612 #[pyo3(get, set)]
613 pub order_iv: Option<String>,
614}
615
616#[pymethods]
617#[pyo3_stub_gen::derive::gen_stub_pymethods]
618impl BybitWsAmendOrderParams {
619 #[new]
621 #[expect(clippy::too_many_arguments)]
622 fn py_new(
623 category: BybitProductType,
624 symbol: String,
625 order_id: Option<String>,
626 order_link_id: Option<String>,
627 qty: Option<String>,
628 price: Option<String>,
629 trigger_price: Option<String>,
630 take_profit: Option<String>,
631 stop_loss: Option<String>,
632 tp_trigger_by: Option<String>,
633 sl_trigger_by: Option<String>,
634 order_iv: Option<String>,
635 ) -> Self {
636 Self {
637 category,
638 symbol,
639 order_id,
640 order_link_id,
641 qty,
642 price,
643 trigger_price,
644 take_profit,
645 stop_loss,
646 tp_trigger_by,
647 sl_trigger_by,
648 order_iv,
649 }
650 }
651}
652
653impl TryFrom<BybitWsAmendOrderParams> for messages::BybitWsAmendOrderParams {
654 type Error = BybitWsError;
655
656 fn try_from(params: BybitWsAmendOrderParams) -> Result<Self, Self::Error> {
657 let tp_trigger_by = params
658 .tp_trigger_by
659 .map(|v| {
660 serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
661 BybitWsError::ClientError(format!("Invalid tp_trigger_by '{v}': {e}"))
662 })
663 })
664 .transpose()?;
665
666 let sl_trigger_by = params
667 .sl_trigger_by
668 .map(|v| {
669 serde_json::from_str::<BybitTriggerType>(&format!("\"{v}\"")).map_err(|e| {
670 BybitWsError::ClientError(format!("Invalid sl_trigger_by '{v}': {e}"))
671 })
672 })
673 .transpose()?;
674
675 Ok(Self {
676 category: params.category,
677 symbol: Ustr::from(¶ms.symbol),
678 order_id: params.order_id,
679 order_link_id: params.order_link_id,
680 qty: params.qty,
681 price: params.price,
682 trigger_price: params.trigger_price,
683 take_profit: params.take_profit,
684 stop_loss: params.stop_loss,
685 tp_trigger_by,
686 sl_trigger_by,
687 order_iv: params.order_iv,
688 })
689 }
690}
691
692impl From<messages::BybitWsAmendOrderParams> for BybitWsAmendOrderParams {
693 fn from(params: messages::BybitWsAmendOrderParams) -> Self {
694 let tp_trigger_by = params.tp_trigger_by.map(|v| {
695 serde_json::to_string(&v)
696 .expect("Failed to serialize BybitTriggerType")
697 .trim_matches('"')
698 .to_string()
699 });
700 let sl_trigger_by = params.sl_trigger_by.map(|v| {
701 serde_json::to_string(&v)
702 .expect("Failed to serialize BybitTriggerType")
703 .trim_matches('"')
704 .to_string()
705 });
706
707 Self {
708 category: params.category,
709 symbol: params.symbol.to_string(),
710 order_id: params.order_id,
711 order_link_id: params.order_link_id,
712 qty: params.qty,
713 price: params.price,
714 trigger_price: params.trigger_price,
715 take_profit: params.take_profit,
716 stop_loss: params.stop_loss,
717 tp_trigger_by,
718 sl_trigger_by,
719 order_iv: params.order_iv,
720 }
721 }
722}
723
724#[pyclass(from_py_object)]
726#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")]
727#[derive(Clone, Debug)]
728pub struct BybitWsCancelOrderParams {
729 #[pyo3(get, set)]
730 pub category: BybitProductType,
731 #[pyo3(get, set)]
732 pub symbol: String,
733 #[pyo3(get, set)]
734 pub order_id: Option<String>,
735 #[pyo3(get, set)]
736 pub order_link_id: Option<String>,
737}
738
739#[pymethods]
740#[pyo3_stub_gen::derive::gen_stub_pymethods]
741impl BybitWsCancelOrderParams {
742 #[new]
744 fn py_new(
745 category: BybitProductType,
746 symbol: String,
747 order_id: Option<String>,
748 order_link_id: Option<String>,
749 ) -> Self {
750 Self {
751 category,
752 symbol,
753 order_id,
754 order_link_id,
755 }
756 }
757}
758
759impl TryFrom<BybitWsCancelOrderParams> for messages::BybitWsCancelOrderParams {
760 type Error = BybitWsError;
761
762 fn try_from(params: BybitWsCancelOrderParams) -> Result<Self, Self::Error> {
763 Ok(Self {
764 category: params.category,
765 symbol: Ustr::from(¶ms.symbol),
766 order_id: params.order_id,
767 order_link_id: params.order_link_id,
768 })
769 }
770}
771
772impl From<messages::BybitWsCancelOrderParams> for BybitWsCancelOrderParams {
773 fn from(params: messages::BybitWsCancelOrderParams) -> Self {
774 Self {
775 category: params.category,
776 symbol: params.symbol.to_string(),
777 order_id: params.order_id,
778 order_link_id: params.order_link_id,
779 }
780 }
781}
782
783#[pyclass(from_py_object)]
785#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")]
786#[derive(Clone, Debug)]
787pub struct BybitTickersParams {
788 #[pyo3(get, set)]
789 pub category: BybitProductType,
790 #[pyo3(get, set)]
791 pub symbol: Option<String>,
792 #[pyo3(get, set)]
793 pub base_coin: Option<String>,
794 #[pyo3(get, set)]
795 pub exp_date: Option<String>,
796}
797
798#[pymethods]
799#[pyo3_stub_gen::derive::gen_stub_pymethods]
800impl BybitTickersParams {
801 #[new]
806 #[pyo3(signature = (category, symbol=None, base_coin=None, exp_date=None))]
807 fn py_new(
808 category: BybitProductType,
809 symbol: Option<String>,
810 base_coin: Option<String>,
811 exp_date: Option<String>,
812 ) -> Self {
813 Self {
814 category,
815 symbol,
816 base_coin,
817 exp_date,
818 }
819 }
820}
821
822impl From<BybitTickersParams> for crate::http::query::BybitTickersParams {
823 fn from(params: BybitTickersParams) -> Self {
824 Self {
825 category: params.category,
826 symbol: params.symbol,
827 base_coin: params.base_coin,
828 exp_date: params.exp_date,
829 }
830 }
831}
832
833#[pyclass(from_py_object)]
839#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")]
840#[derive(Debug, Clone, Default)]
841pub struct BybitNativeTpSlParams {
842 #[pyo3(get, set)]
843 pub take_profit: Option<String>,
844 #[pyo3(get, set)]
845 pub stop_loss: Option<String>,
846 #[pyo3(get, set)]
847 pub tp_trigger_by: Option<String>,
848 #[pyo3(get, set)]
849 pub sl_trigger_by: Option<String>,
850 #[pyo3(get, set)]
851 pub tp_order_type: Option<String>,
852 #[pyo3(get, set)]
853 pub sl_order_type: Option<String>,
854 #[pyo3(get, set)]
855 pub tp_limit_price: Option<String>,
856 #[pyo3(get, set)]
857 pub sl_limit_price: Option<String>,
858 #[pyo3(get, set)]
859 pub tpsl_mode: Option<String>,
860 #[pyo3(get, set)]
861 pub close_on_trigger: Option<bool>,
862 #[pyo3(get, set)]
863 pub order_iv: Option<String>,
864 #[pyo3(get, set)]
865 pub mmp: Option<bool>,
866}
867
868#[pymethods]
869#[pyo3_stub_gen::derive::gen_stub_pymethods]
870impl BybitNativeTpSlParams {
871 #[new]
882 #[pyo3(signature = (
883 take_profit=None,
884 stop_loss=None,
885 tp_trigger_by=None,
886 sl_trigger_by=None,
887 tp_order_type=None,
888 sl_order_type=None,
889 tp_limit_price=None,
890 sl_limit_price=None,
891 tpsl_mode=None,
892 close_on_trigger=None,
893 order_iv=None,
894 mmp=None,
895 ))]
896 #[expect(clippy::too_many_arguments)]
897 fn py_new(
898 take_profit: Option<String>,
899 stop_loss: Option<String>,
900 tp_trigger_by: Option<String>,
901 sl_trigger_by: Option<String>,
902 tp_order_type: Option<String>,
903 sl_order_type: Option<String>,
904 tp_limit_price: Option<String>,
905 sl_limit_price: Option<String>,
906 tpsl_mode: Option<String>,
907 close_on_trigger: Option<bool>,
908 order_iv: Option<String>,
909 mmp: Option<bool>,
910 ) -> Self {
911 Self {
912 take_profit,
913 stop_loss,
914 tp_trigger_by,
915 sl_trigger_by,
916 tp_order_type,
917 sl_order_type,
918 tp_limit_price,
919 sl_limit_price,
920 tpsl_mode,
921 close_on_trigger,
922 order_iv,
923 mmp,
924 }
925 }
926}
927
928impl TryFrom<BybitNativeTpSlParams> for RustNativeTpSlParams {
929 type Error = anyhow::Error;
930
931 fn try_from(params: BybitNativeTpSlParams) -> anyhow::Result<Self> {
932 Ok(Self {
933 take_profit: params.take_profit,
934 stop_loss: params.stop_loss,
935 tp_trigger_by: params
936 .tp_trigger_by
937 .as_deref()
938 .map(parse_trigger_type)
939 .transpose()?,
940 sl_trigger_by: params
941 .sl_trigger_by
942 .as_deref()
943 .map(parse_trigger_type)
944 .transpose()?,
945 tp_order_type: params
946 .tp_order_type
947 .as_deref()
948 .map(parse_tp_sl_order_type)
949 .transpose()?,
950 sl_order_type: params
951 .sl_order_type
952 .as_deref()
953 .map(parse_tp_sl_order_type)
954 .transpose()?,
955 tp_limit_price: params.tp_limit_price,
956 sl_limit_price: params.sl_limit_price,
957 tpsl_mode: params
958 .tpsl_mode
959 .as_deref()
960 .map(parse_tpsl_mode)
961 .transpose()?,
962 close_on_trigger: params.close_on_trigger,
963 order_iv: params.order_iv,
964 mmp: params.mmp,
965 })
966 }
967}