1use pyo3::prelude::*;
17
18use crate::{
19 common::{
20 enums::BybitProductType,
21 parse::{parse_tp_sl_order_type, parse_tpsl_mode, parse_trigger_type},
22 },
23 http::query::BybitNativeTpSlParams as RustNativeTpSlParams,
24};
25
26#[cfg(test)]
27mod tests {
28 use rstest::rstest;
29
30 use super::*;
31 use crate::common::enums::{BybitOrderType, BybitTpSlMode, BybitTriggerType};
32
33 #[rstest]
34 fn test_native_tp_sl_params_try_from_accepts_valid_enums() {
35 let params = BybitNativeTpSlParams {
36 take_profit: Some("55000".to_string()),
37 stop_loss: Some("47000".to_string()),
38 tp_trigger_by: Some("LastPrice".to_string()),
39 sl_trigger_by: Some("MarkPrice".to_string()),
40 tp_order_type: Some("Limit".to_string()),
41 sl_order_type: Some("Market".to_string()),
42 tpsl_mode: Some("Partial".to_string()),
43 ..Default::default()
44 };
45
46 let native = RustNativeTpSlParams::try_from(params).unwrap();
47
48 assert_eq!(native.tp_trigger_by, Some(BybitTriggerType::LastPrice));
49 assert_eq!(native.sl_trigger_by, Some(BybitTriggerType::MarkPrice));
50 assert_eq!(native.tp_order_type, Some(BybitOrderType::Limit));
51 assert_eq!(native.sl_order_type, Some(BybitOrderType::Market));
52 assert_eq!(native.tpsl_mode, Some(BybitTpSlMode::Partial));
53 }
54
55 #[rstest]
56 #[case("tp_trigger_by")]
57 #[case("sl_trigger_by")]
58 #[case("tp_order_type")]
59 #[case("sl_order_type")]
60 #[case("tpsl_mode")]
61 fn test_native_tp_sl_params_try_from_rejects_invalid_enum(#[case] field: &str) {
62 let mut params = BybitNativeTpSlParams::default();
63 match field {
64 "tp_trigger_by" => params.tp_trigger_by = Some("garbage".to_string()),
65 "sl_trigger_by" => params.sl_trigger_by = Some("garbage".to_string()),
66 "tp_order_type" => params.tp_order_type = Some("garbage".to_string()),
67 "sl_order_type" => params.sl_order_type = Some("garbage".to_string()),
68 "tpsl_mode" => params.tpsl_mode = Some("garbage".to_string()),
69 _ => unreachable!(),
70 }
71
72 let err = RustNativeTpSlParams::try_from(params).unwrap_err();
73 assert!(err.to_string().contains("garbage"));
74 }
75
76 #[rstest]
77 fn test_native_tp_sl_params_try_from_rejects_unknown_tpsl_mode() {
78 let params = BybitNativeTpSlParams {
81 tpsl_mode: Some("Unknown".to_string()),
82 ..Default::default()
83 };
84
85 let err = RustNativeTpSlParams::try_from(params).unwrap_err();
86 assert!(err.to_string().contains("invalid Bybit TP/SL mode"));
87 }
88}
89
90#[pyclass(from_py_object)]
92#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")]
93#[derive(Clone, Debug)]
94pub struct BybitTickersParams {
95 #[pyo3(get, set)]
96 pub category: BybitProductType,
97 #[pyo3(get, set)]
98 pub symbol: Option<String>,
99 #[pyo3(get, set)]
100 pub base_coin: Option<String>,
101 #[pyo3(get, set)]
102 pub exp_date: Option<String>,
103}
104
105#[pymethods]
106#[pyo3_stub_gen::derive::gen_stub_pymethods]
107impl BybitTickersParams {
108 #[new]
113 #[pyo3(signature = (category, symbol=None, base_coin=None, exp_date=None))]
114 fn py_new(
115 category: BybitProductType,
116 symbol: Option<String>,
117 base_coin: Option<String>,
118 exp_date: Option<String>,
119 ) -> Self {
120 Self {
121 category,
122 symbol,
123 base_coin,
124 exp_date,
125 }
126 }
127}
128
129impl From<BybitTickersParams> for crate::http::query::BybitTickersParams {
130 fn from(params: BybitTickersParams) -> Self {
131 Self {
132 category: params.category,
133 symbol: params.symbol,
134 base_coin: params.base_coin,
135 exp_date: params.exp_date,
136 }
137 }
138}
139
140#[pyclass(from_py_object)]
145#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")]
146#[derive(Debug, Clone, Default)]
147pub struct BybitNativeTpSlParams {
148 #[pyo3(get, set)]
149 pub take_profit: Option<String>,
150 #[pyo3(get, set)]
151 pub stop_loss: Option<String>,
152 #[pyo3(get, set)]
153 pub tp_trigger_by: Option<String>,
154 #[pyo3(get, set)]
155 pub sl_trigger_by: Option<String>,
156 #[pyo3(get, set)]
157 pub tp_order_type: Option<String>,
158 #[pyo3(get, set)]
159 pub sl_order_type: Option<String>,
160 #[pyo3(get, set)]
161 pub tp_limit_price: Option<String>,
162 #[pyo3(get, set)]
163 pub sl_limit_price: Option<String>,
164 #[pyo3(get, set)]
165 pub tpsl_mode: Option<String>,
166 #[pyo3(get, set)]
167 pub close_on_trigger: Option<bool>,
168 #[pyo3(get, set)]
169 pub order_iv: Option<String>,
170 #[pyo3(get, set)]
171 pub mmp: Option<bool>,
172}
173
174#[pymethods]
175#[pyo3_stub_gen::derive::gen_stub_pymethods]
176impl BybitNativeTpSlParams {
177 #[new]
188 #[pyo3(signature = (
189 take_profit=None,
190 stop_loss=None,
191 tp_trigger_by=None,
192 sl_trigger_by=None,
193 tp_order_type=None,
194 sl_order_type=None,
195 tp_limit_price=None,
196 sl_limit_price=None,
197 tpsl_mode=None,
198 close_on_trigger=None,
199 order_iv=None,
200 mmp=None,
201 ))]
202 #[expect(clippy::too_many_arguments)]
203 fn py_new(
204 take_profit: Option<String>,
205 stop_loss: Option<String>,
206 tp_trigger_by: Option<String>,
207 sl_trigger_by: Option<String>,
208 tp_order_type: Option<String>,
209 sl_order_type: Option<String>,
210 tp_limit_price: Option<String>,
211 sl_limit_price: Option<String>,
212 tpsl_mode: Option<String>,
213 close_on_trigger: Option<bool>,
214 order_iv: Option<String>,
215 mmp: Option<bool>,
216 ) -> Self {
217 Self {
218 take_profit,
219 stop_loss,
220 tp_trigger_by,
221 sl_trigger_by,
222 tp_order_type,
223 sl_order_type,
224 tp_limit_price,
225 sl_limit_price,
226 tpsl_mode,
227 close_on_trigger,
228 order_iv,
229 mmp,
230 }
231 }
232}
233
234impl TryFrom<BybitNativeTpSlParams> for RustNativeTpSlParams {
235 type Error = anyhow::Error;
236
237 fn try_from(params: BybitNativeTpSlParams) -> anyhow::Result<Self> {
238 Ok(Self {
239 take_profit: params.take_profit,
240 stop_loss: params.stop_loss,
241 tp_trigger_by: params
242 .tp_trigger_by
243 .as_deref()
244 .map(parse_trigger_type)
245 .transpose()?,
246 sl_trigger_by: params
247 .sl_trigger_by
248 .as_deref()
249 .map(parse_trigger_type)
250 .transpose()?,
251 tp_order_type: params
252 .tp_order_type
253 .as_deref()
254 .map(parse_tp_sl_order_type)
255 .transpose()?,
256 sl_order_type: params
257 .sl_order_type
258 .as_deref()
259 .map(parse_tp_sl_order_type)
260 .transpose()?,
261 tp_limit_price: params.tp_limit_price,
262 sl_limit_price: params.sl_limit_price,
263 tpsl_mode: params
264 .tpsl_mode
265 .as_deref()
266 .map(parse_tpsl_mode)
267 .transpose()?,
268 close_on_trigger: params.close_on_trigger,
269 order_iv: params.order_iv,
270 mmp: params.mmp,
271 })
272 }
273}