1use 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#[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 #[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(¶ms.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(¶ms.side)
319 .expect("Failed to serialize BybitOrderSide")
320 .trim_matches('"')
321 .to_string();
322 let order_type = serde_json::to_string(¶ms.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#[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 #[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(¶ms.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#[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 #[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(¶ms.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#[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 #[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}