Skip to main content

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}