bybit/models/trading_stop_request.rs
1use crate::prelude::*;
2
3/// Parameters for setting trading stop conditions (take-profit/stop-loss/trailing-stop).
4///
5/// Used to construct a request to the `/v5/position/trading-stop` endpoint to set take-profit,
6/// stop-loss, and trailing-stop conditions for a position. Bots use this to implement automated
7/// exit strategies and manage risk in perpetual futures trading. This struct supports both
8/// full and partial take-profit/stop-loss modes, allowing precise control over position exits.
9///
10/// # Bybit API Reference
11/// According to the Bybit V5 API documentation:
12/// - New version of TP/SL function supports both holding entire position TP/SL orders and
13/// holding partial position TP/SL orders.
14/// - Full position TP/SL orders: This API can be used to modify parameters of existing TP/SL orders.
15/// - Partial position TP/SL orders: This API can only add partial position TP/SL orders.
16/// - Under the new version of TP/SL function, when calling this API to perform one-sided
17/// take profit or stop loss modification on existing TP/SL orders on the holding position,
18/// it will cause the paired tp/sl orders to lose binding relationship.
19#[derive(Clone, Default)]
20pub struct TradingStopRequest<'a> {
21 /// The product category (e.g., Linear, Inverse).
22 ///
23 /// Specifies the instrument type, such as `Linear` for USDT-margined perpetual futures.
24 /// Bots must set this correctly to target the desired contract type.
25 pub category: Category,
26
27 /// The trading pair symbol (e.g., "BTCUSDT").
28 ///
29 /// Identifies the perpetual futures contract for which trading stops are being set.
30 /// Bots must specify a valid symbol to ensure the request targets the correct position.
31 pub symbol: Cow<'a, str>,
32
33 /// The take-profit/stop-loss mode (e.g., "Full", "Partial").
34 ///
35 /// Specifies whether the trading stops apply to the entire position (`Full`) or a portion (`Partial`).
36 /// Bots use this to implement granular exit strategies, such as scaling out of positions.
37 /// This parameter is required according to the Bybit API documentation.
38 pub tpsl_mode: Cow<'a, str>,
39
40 /// The position index (e.g., 0 for one-way mode, 1 or 2 for hedge mode).
41 ///
42 /// Used to identify positions in different position modes.
43 /// - `0`: one-way mode
44 /// - `1`: hedge-mode Buy side
45 /// - `2`: hedge-mode Sell side
46 /// This parameter is required according to the Bybit API documentation.
47 pub position_idx: i32,
48
49 /// The take-profit price (optional).
50 ///
51 /// The price at which the position will close for a profit. Bots use this to lock in gains
52 /// automatically, typically based on technical indicators or predefined targets.
53 /// Cannot be less than 0, 0 means cancel TP.
54 pub take_profit: Option<f64>,
55
56 /// The stop-loss price (optional).
57 ///
58 /// The price at which the position will close to limit losses. Bots use this to manage
59 /// downside risk, protecting capital during adverse market movements.
60 /// Cannot be less than 0, 0 means cancel SL.
61 pub stop_loss: Option<f64>,
62
63 /// The trailing stop by price distance (optional).
64 ///
65 /// Trailing stop by price distance. Cannot be less than 0, 0 means cancel TS.
66 /// Bots use this to implement dynamic stop-loss strategies that follow favorable price movements.
67 pub trailing_stop: Option<f64>,
68
69 /// The trigger type for take-profit (optional).
70 ///
71 /// Specifies the price type used to trigger the take-profit order (e.g., `LastPrice`,
72 /// `MarkPrice`, `IndexPrice`). Bots should choose a trigger type that aligns with their
73 /// strategy to balance responsiveness and stability.
74 pub tp_trigger_by: Option<Cow<'a, str>>,
75
76 /// The trigger type for stop-loss (optional).
77 ///
78 /// Specifies the price type used to trigger the stop-loss order. Bots should select a
79 /// trigger type that minimizes slippage while ensuring timely execution in volatile markets.
80 pub sl_trigger_by: Option<Cow<'a, str>>,
81
82 /// The trailing stop trigger price (optional).
83 ///
84 /// Trailing stop will be triggered when this price is reached **only**.
85 /// Bots use this to set an activation price for trailing stops, ensuring they only
86 /// become active after a certain profit level is reached.
87 pub active_price: Option<f64>,
88
89 /// The size of the take-profit order (optional).
90 ///
91 /// The quantity of the position to close when the take-profit is triggered, used in
92 /// TP/SL partial mode. Note: the value of tp_size and sl_size must equal.
93 /// Bots use this to scale out of positions incrementally, optimizing profit capture.
94 pub tp_size: Option<f64>,
95
96 /// The size of the stop-loss order (optional).
97 ///
98 /// The quantity of the position to close when the stop-loss is triggered, used in
99 /// TP/SL partial mode. Note: the value of tp_size and sl_size must equal.
100 /// Bots use this to limit losses on specific portions of a position.
101 pub sl_size: Option<f64>,
102
103 /// The limit price for the take-profit order (optional).
104 ///
105 /// The specific price for a `Limit` take-profit order. Only works when tpslMode=Partial
106 /// and tpOrderType=Limit. Bots use this to ensure the take-profit order executes at a
107 /// favorable price, avoiding slippage in stable markets.
108 pub tp_limit_price: Option<f64>,
109
110 /// The limit price for the stop-loss order (optional).
111 ///
112 /// The specific price for a `Limit` stop-loss order. Only works when tpslMode=Partial
113 /// and slOrderType=Limit. Bots use this cautiously, as limit orders may not execute
114 /// in fast-moving markets, increasing risk exposure.
115 pub sl_limit_price: Option<f64>,
116
117 /// The order type when take profit is triggered (optional).
118 ///
119 /// The order type when take profit is triggered. `Market` (default), `Limit`.
120 /// For tpslMode=Full, it only supports tpOrderType="Market".
121 /// Bots can use `Limit` orders to target specific exit prices, reducing slippage.
122 pub tp_order_type: Option<OrderType>,
123
124 /// The order type when stop loss is triggered (optional).
125 ///
126 /// The order type when stop loss is triggered. `Market` (default), `Limit`.
127 /// For tpslMode=Full, it only supports slOrderType="Market".
128 /// Bots typically use `Market` orders for stop-losses to ensure execution.
129 pub sl_order_type: Option<OrderType>,
130}
131
132impl<'a> TradingStopRequest<'a> {
133 /// Constructs a new TradingStop request with specified parameters.
134 ///
135 /// Allows full customization of the trading stop request. Bots should use this to define
136 /// the exact symbol, category, and trading stop parameters to align with their risk
137 /// management strategy.
138 ///
139 /// # Arguments
140 /// * `category` - The product category (Linear, Inverse)
141 /// * `symbol` - The trading pair symbol (e.g., "BTCUSDT")
142 /// * `tpsl_mode` - The TP/SL mode ("Full" or "Partial")
143 /// * `position_idx` - The position index (0: one-way, 1: hedge buy, 2: hedge sell)
144 /// * `take_profit` - The take-profit price (optional, 0 means cancel)
145 /// * `stop_loss` - The stop-loss price (optional, 0 means cancel)
146 /// * `trailing_stop` - The trailing stop distance (optional, 0 means cancel)
147 /// * `tp_trigger_by` - The trigger type for take-profit (optional)
148 /// * `sl_trigger_by` - The trigger type for stop-loss (optional)
149 /// * `active_price` - The trailing stop trigger price (optional)
150 /// * `tp_size` - The take-profit size for partial mode (optional)
151 /// * `sl_size` - The stop-loss size for partial mode (optional)
152 /// * `tp_limit_price` - The limit price for take-profit (optional)
153 /// * `sl_limit_price` - The limit price for stop-loss (optional)
154 /// * `tp_order_type` - The order type for take-profit (optional)
155 /// * `sl_order_type` - The order type for stop-loss (optional)
156 pub fn new(
157 category: Category,
158 symbol: &'a str,
159 tpsl_mode: &'a str,
160 position_idx: i32,
161 take_profit: Option<f64>,
162 stop_loss: Option<f64>,
163 trailing_stop: Option<f64>,
164 tp_trigger_by: Option<&'a str>,
165 sl_trigger_by: Option<&'a str>,
166 active_price: Option<f64>,
167 tp_size: Option<f64>,
168 sl_size: Option<f64>,
169 tp_limit_price: Option<f64>,
170 sl_limit_price: Option<f64>,
171 tp_order_type: Option<OrderType>,
172 sl_order_type: Option<OrderType>,
173 ) -> Self {
174 Self {
175 category,
176 symbol: Cow::Borrowed(symbol),
177 tpsl_mode: Cow::Borrowed(tpsl_mode),
178 position_idx,
179 take_profit,
180 stop_loss,
181 trailing_stop,
182 tp_trigger_by: tp_trigger_by.map(Cow::Borrowed),
183 sl_trigger_by: sl_trigger_by.map(Cow::Borrowed),
184 active_price,
185 tp_size,
186 sl_size,
187 tp_limit_price,
188 sl_limit_price,
189 tp_order_type,
190 sl_order_type,
191 }
192 }
193 /// Creates a default TradingStop request.
194 ///
195 /// Returns a request with `category` set to `Linear`, `symbol` set to `"BTCUSDT"`,
196 /// `tpsl_mode` set to `"Full"`, `position_idx` set to `0` (one-way mode),
197 /// and all other fields unset. Suitable for testing but should be customized for
198 /// production to match specific trading needs.
199 pub fn default() -> TradingStopRequest<'a> {
200 TradingStopRequest::new(
201 Category::Linear,
202 "BTCUSDT",
203 "Full",
204 0,
205 None,
206 None,
207 None,
208 None,
209 None,
210 None,
211 None,
212 None,
213 None,
214 None,
215 None,
216 None,
217 )
218 }
219
220 /// Validates the request parameters according to Bybit API constraints.
221 ///
222 /// Bots should call this method before sending the request to ensure compliance
223 /// with API limits and requirements.
224 ///
225 /// # Returns
226 /// * `Ok(())` if validation passes
227 /// * `Err(String)` with error message if validation fails
228 pub fn validate(&self) -> Result<(), String> {
229 // Validate tpsl_mode
230 if self.tpsl_mode != "Full" && self.tpsl_mode != "Partial" {
231 return Err("tpsl_mode must be either 'Full' or 'Partial'".to_string());
232 }
233
234 // Validate position_idx
235 if self.position_idx < 0 || self.position_idx > 2 {
236 return Err("position_idx must be 0, 1, or 2".to_string());
237 }
238
239 // Validate take_profit and stop_loss are not negative
240 if let Some(tp) = self.take_profit {
241 if tp < 0.0 {
242 return Err("take_profit cannot be negative".to_string());
243 }
244 }
245
246 if let Some(sl) = self.stop_loss {
247 if sl < 0.0 {
248 return Err("stop_loss cannot be negative".to_string());
249 }
250 }
251
252 if let Some(ts) = self.trailing_stop {
253 if ts < 0.0 {
254 return Err("trailing_stop cannot be negative".to_string());
255 }
256 }
257
258 // Validate tp_size and sl_size for partial mode
259 if self.tpsl_mode == "Partial" {
260 if let (Some(tp_size), Some(sl_size)) = (self.tp_size, self.sl_size) {
261 if tp_size != sl_size {
262 return Err("tp_size and sl_size must be equal in partial mode".to_string());
263 }
264 } else if self.tp_size.is_some() || self.sl_size.is_some() {
265 return Err("both tp_size and sl_size must be provided in partial mode".to_string());
266 }
267 }
268
269 // Validate order types for full mode
270 if self.tpsl_mode == "Full" {
271 if let Some(tp_order_type) = &self.tp_order_type {
272 if tp_order_type != &OrderType::Market {
273 return Err("tp_order_type must be 'Market' for full mode".to_string());
274 }
275 }
276 if let Some(sl_order_type) = &self.sl_order_type {
277 if sl_order_type != &OrderType::Market {
278 return Err("sl_order_type must be 'Market' for full mode".to_string());
279 }
280 }
281 }
282
283 // Validate limit prices are only used with limit order types
284 if self.tp_limit_price.is_some() && self.tp_order_type != Some(OrderType::Limit) {
285 return Err("tp_limit_price requires tp_order_type to be 'Limit'".to_string());
286 }
287
288 if self.sl_limit_price.is_some() && self.sl_order_type != Some(OrderType::Limit) {
289 return Err("sl_limit_price requires sl_order_type to be 'Limit'".to_string());
290 }
291
292 // Validate limit prices are only used in partial mode
293 if (self.tp_limit_price.is_some() || self.sl_limit_price.is_some())
294 && self.tpsl_mode != "Partial"
295 {
296 return Err("limit prices can only be used in partial mode".to_string());
297 }
298
299 Ok(())
300 }
301}