nautilus_bitmex/http/
query.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use chrono::{DateTime, Utc};
17use derive_builder::Builder;
18use serde::{self, Deserialize, Serialize, Serializer};
19use serde_json::Value;
20
21/// Serialize a JSON Value as a string for URL encoding.
22fn serialize_json_as_string<S>(value: &Option<Value>, serializer: S) -> Result<S::Ok, S::Error>
23where
24    S: Serializer,
25{
26    match value {
27        Some(v) => serializer.serialize_str(&v.to_string()),
28        None => serializer.serialize_none(),
29    }
30}
31
32use crate::common::enums::{
33    BitmexContingencyType, BitmexExecInstruction, BitmexOrderType, BitmexPegPriceType, BitmexSide,
34    BitmexTimeInForce,
35};
36
37fn serialize_string_vec<S>(values: &Option<Vec<String>>, serializer: S) -> Result<S::Ok, S::Error>
38where
39    S: serde::Serializer,
40{
41    match values {
42        Some(vec) => serializer.serialize_str(&vec.join(",")),
43        None => serializer.serialize_none(),
44    }
45}
46
47/// Parameters for the GET /trade endpoint.
48#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
49#[builder(default)]
50#[builder(setter(into, strip_option))]
51#[serde(rename_all = "camelCase")]
52pub struct GetTradeParams {
53    /// Instrument symbol. Send a bare series (e.g., XBT) to get data for the nearest expiring contract in that series.  You can also send a timeframe, e.g. `XBT:quarterly`. Timeframes are `nearest`, `daily`, `weekly`, `monthly`, `quarterly`, `biquarterly`, and `perpetual`.
54    pub symbol: Option<String>,
55    /// Generic table filter. Send JSON key/value pairs, such as `{"key": "value"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://www.bitmex.com/app/restAPI#Timestamp-Filters) for more details.
56    #[serde(
57        skip_serializing_if = "Option::is_none",
58        serialize_with = "serialize_json_as_string"
59    )]
60    pub filter: Option<Value>,
61    /// Array of column names to fetch. If omitted, will return all columns.  Note that this method will always return item keys, even when not specified, so you may receive more columns that you expect.
62    #[serde(
63        skip_serializing_if = "Option::is_none",
64        serialize_with = "serialize_json_as_string"
65    )]
66    pub columns: Option<Value>,
67    /// Number of results to fetch.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub count: Option<i32>,
70    /// Starting point for results.
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub start: Option<i32>,
73    /// If true, will sort results newest first.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub reverse: Option<bool>,
76    /// Starting date filter for results.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub start_time: Option<DateTime<Utc>>,
79    /// Ending date filter for results.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub end_time: Option<DateTime<Utc>>,
82}
83
84/// Parameters for the GET /order endpoint.
85#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
86#[builder(default)]
87#[builder(setter(into, strip_option))]
88#[serde(rename_all = "camelCase")]
89pub struct GetOrderParams {
90    /// Instrument symbol. Send a bare series (e.g., XBT) to get data for the nearest expiring contract in that series.  You can also send a timeframe, e.g. `XBT:quarterly`. Timeframes are `nearest`, `daily`, `weekly`, `monthly`, `quarterly`, `biquarterly`, and `perpetual`.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub symbol: Option<String>,
93    /// Generic table filter. Send JSON key/value pairs, such as `{"key": "value"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://www.bitmex.com/app/restAPI#Timestamp-Filters) for more details.
94    #[serde(
95        skip_serializing_if = "Option::is_none",
96        serialize_with = "serialize_json_as_string"
97    )]
98    pub filter: Option<Value>,
99    /// Array of column names to fetch. If omitted, will return all columns.  Note that this method will always return item keys, even when not specified, so you may receive more columns that you expect.
100    #[serde(
101        skip_serializing_if = "Option::is_none",
102        serialize_with = "serialize_json_as_string"
103    )]
104    pub columns: Option<Value>,
105    /// Number of results to fetch.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub count: Option<i32>,
108    /// Starting point for results.
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub start: Option<i32>,
111    /// If true, will sort results newest first.
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub reverse: Option<bool>,
114    /// Starting date filter for results.
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub start_time: Option<DateTime<Utc>>,
117    /// Ending date filter for results.
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub end_time: Option<DateTime<Utc>>,
120}
121
122/// Parameters for the POST /order endpoint.
123#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
124#[builder(default)]
125#[builder(setter(into, strip_option))]
126#[serde(rename_all = "camelCase")]
127pub struct PostOrderParams {
128    /// Instrument symbol. e.g. 'XBTUSD'.
129    pub symbol: String,
130    /// Order side. Valid options: Buy, Sell. Defaults to 'Buy' unless `orderQty` is negative.
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub side: Option<BitmexSide>,
133    /// Order quantity in units of the instrument (i.e. contracts).
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub order_qty: Option<u32>,
136    /// Optional limit price for `Limit`, `StopLimit`, and `LimitIfTouched` orders.
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub price: Option<f64>,
139    /// Optional quantity to display in the book. Use 0 for a fully hidden order.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub display_qty: Option<u32>,
142    /// Optional trigger price for `Stop`, `StopLimit`, `MarketIfTouched`, and `LimitIfTouched` orders. Use a price below the current price for stop-sell orders and buy-if-touched orders. Use `execInst` of `MarkPrice` or `LastPrice` to define the current price used for triggering.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub stop_px: Option<f64>,
145    /// Optional Client Order ID. This clOrdID will come back on the order and any related executions.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    #[serde(rename = "clOrdID")]
148    pub cl_ord_id: Option<String>,
149    /// Optional Client Order Link ID for contingent orders.
150    #[serde(skip_serializing_if = "Option::is_none")]
151    #[serde(rename = "clOrdLinkID")]
152    pub cl_ord_link_id: Option<String>,
153    /// Optional trailing offset from the current price for `Stop`, `StopLimit`, `MarketIfTouched`, and `LimitIfTouched` orders; use a negative offset for stop-sell orders and buy-if-touched orders. Optional offset from the peg price for 'Pegged' orders.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub peg_offset_value: Option<f64>,
156    /// Optional peg price type. Valid options: `LastPeg`, `MidPricePeg`, `MarketPeg`, `PrimaryPeg`, `TrailingStopPeg`.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub peg_price_type: Option<BitmexPegPriceType>,
159    /// Order type. Valid options: Market, Limit, Stop, `StopLimit`, `MarketIfTouched`, `LimitIfTouched`, Pegged. Defaults to `Limit` when `price` is specified. Defaults to `Stop` when `stopPx` is specified. Defaults to `StopLimit` when `price` and `stopPx` are specified.
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub ord_type: Option<BitmexOrderType>,
162    /// Time in force. Valid options: `Day`, `GoodTillCancel`, `ImmediateOrCancel`, `FillOrKill`. Defaults to `GoodTillCancel` for `Limit`, `StopLimit`, and `LimitIfTouched` orders.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub time_in_force: Option<BitmexTimeInForce>,
165    /// Optional execution instructions. Valid options: `ParticipateDoNotInitiate`, `AllOrNone`, `MarkPrice`, `IndexPrice`, `LastPrice`, `Close`, `ReduceOnly`, Fixed. `AllOrNone` instruction requires `displayQty` to be 0. `MarkPrice`, `IndexPrice` or `LastPrice` instruction valid for `Stop`, `StopLimit`, `MarketIfTouched`, and `LimitIfTouched` orders.
166    #[serde(
167        serialize_with = "serialize_exec_instructions_optional",
168        skip_serializing_if = "is_exec_inst_empty"
169    )]
170    pub exec_inst: Option<Vec<BitmexExecInstruction>>,
171    /// Deprecated: linked orders are not supported after 2018/11/10.
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub contingency_type: Option<BitmexContingencyType>,
174    /// Optional order annotation. e.g. 'Take profit'.
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub text: Option<String>,
177}
178
179fn is_exec_inst_empty(exec_inst: &Option<Vec<BitmexExecInstruction>>) -> bool {
180    exec_inst.as_ref().is_none_or(Vec::is_empty)
181}
182
183fn serialize_exec_instructions_optional<S>(
184    instructions: &Option<Vec<BitmexExecInstruction>>,
185    serializer: S,
186) -> Result<S::Ok, S::Error>
187where
188    S: serde::Serializer,
189{
190    match instructions {
191        Some(inst) if !inst.is_empty() => {
192            let joined = inst
193                .iter()
194                .map(std::string::ToString::to_string)
195                .collect::<Vec<_>>()
196                .join(",");
197            serializer.serialize_some(&joined)
198        }
199        _ => serializer.serialize_none(),
200    }
201}
202
203/// Parameters for the DELETE /order endpoint.
204#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
205#[builder(default)]
206#[builder(setter(into, strip_option))]
207#[serde(rename_all = "camelCase")]
208pub struct DeleteOrderParams {
209    /// Order ID(s) (venue-assigned).
210    #[serde(
211        skip_serializing_if = "Option::is_none",
212        serialize_with = "serialize_string_vec",
213        rename = "orderID"
214    )]
215    pub order_id: Option<Vec<String>>,
216    /// Client Order ID(s). See POST /order.
217    #[serde(
218        skip_serializing_if = "Option::is_none",
219        serialize_with = "serialize_string_vec",
220        rename = "clOrdID"
221    )]
222    pub cl_ord_id: Option<Vec<String>>,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    /// Optional cancellation annotation. e.g. 'Spread Exceeded'.
225    pub text: Option<String>,
226}
227
228/// Parameters for the DELETE /order/all endpoint.
229///
230/// # References
231///
232/// <https://www.bitmex.com/api/explorer/#!/Order/Order_cancelAll>
233#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
234#[builder(default)]
235#[builder(setter(into, strip_option))]
236#[serde(rename_all = "camelCase")]
237pub struct DeleteAllOrdersParams {
238    /// Optional symbol. If provided, only cancels orders for that symbol.
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub symbol: Option<String>,
241    /// Optional filter for cancellation. Send JSON key/value pairs, such as `{"side": "Buy"}`.
242    #[serde(
243        skip_serializing_if = "Option::is_none",
244        serialize_with = "serialize_json_as_string"
245    )]
246    pub filter: Option<Value>,
247    /// Optional cancellation annotation. e.g. 'Spread Exceeded'.
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub text: Option<String>,
250}
251
252/// Parameters for the PUT /order endpoint.
253#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
254#[builder(default)]
255#[builder(setter(into, strip_option))]
256#[serde(rename_all = "camelCase")]
257pub struct PutOrderParams {
258    /// Order ID
259    #[serde(rename = "orderID")]
260    pub order_id: Option<String>,
261    /// Client Order ID. See POST /order.
262    #[serde(rename = "origClOrdID")]
263    pub orig_cl_ord_id: Option<String>,
264    /// Optional new Client Order ID, requires `origClOrdID`.
265    #[serde(rename = "clOrdID")]
266    pub cl_ord_id: Option<String>,
267    /// Optional order quantity in units of the instrument (i.e. contracts).
268    pub order_qty: Option<u32>,
269    /// Optional leaves quantity in units of the instrument (i.e. contracts). Useful for amending partially filled orders.
270    pub leaves_qty: Option<u32>,
271    /// Optional limit price for `Limit`, `StopLimit`, and `LimitIfTouched` orders.
272    pub price: Option<f64>,
273    /// Optional trigger price for `Stop`, `StopLimit`, `MarketIfTouched`, and `LimitIfTouched` orders. Use a price below the current price for stop-sell orders and buy-if-touched orders.
274    pub stop_px: Option<f64>,
275    /// Optional trailing offset from the current price for `Stop`, `StopLimit`, `MarketIfTouched`, and `LimitIfTouched` orders; use a negative offset for stop-sell orders and buy-if-touched orders. Optional offset from the peg price for 'Pegged' orders.
276    pub peg_offset_value: Option<f64>,
277    /// Optional amend annotation. e.g. 'Adjust skew'.
278    pub text: Option<String>,
279}
280
281/// Parameters for the GET /execution/tradeHistory endpoint.
282#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
283#[builder(default)]
284#[builder(setter(into, strip_option))]
285#[serde(rename_all = "camelCase")]
286pub struct GetExecutionParams {
287    /// Instrument symbol. Send a bare series (e.g. XBT) to get data for the nearest expiring contract in that series.  You can also send a timeframe, e.g. `XBT:quarterly`. Timeframes are `nearest`, `daily`, `weekly`, `monthly`, `quarterly`, `biquarterly`, and `perpetual`.
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub symbol: Option<String>,
290    /// Generic table filter. Send JSON key/value pairs, such as `{"key": "value"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://www.bitmex.com/app/restAPI#Timestamp-Filters) for more details.
291    #[serde(
292        skip_serializing_if = "Option::is_none",
293        serialize_with = "serialize_json_as_string"
294    )]
295    pub filter: Option<Value>,
296    /// Array of column names to fetch. If omitted, will return all columns.  Note that this method will always return item keys, even when not specified, so you may receive more columns that you expect.
297    #[serde(
298        skip_serializing_if = "Option::is_none",
299        serialize_with = "serialize_json_as_string"
300    )]
301    pub columns: Option<Value>,
302    /// Number of results to fetch.
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub count: Option<i32>,
305    /// Starting point for results.
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub start: Option<i32>,
308    /// If true, will sort results newest first.
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub reverse: Option<bool>,
311    /// Starting date filter for results.
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub start_time: Option<DateTime<Utc>>,
314    /// Ending date filter for results.
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub end_time: Option<DateTime<Utc>>,
317}
318
319/// Parameters for the POST /order/bulk endpoint.
320#[derive(Clone, Debug, Deserialize, Serialize, Default)]
321#[serde(rename_all = "camelCase")]
322pub struct PostOrderBulkParams {
323    /// Array of order parameters.
324    pub orders: Vec<PostOrderParams>,
325}
326
327/// Parameters for the PUT /order/bulk endpoint.
328#[derive(Clone, Debug, Deserialize, Serialize, Default)]
329#[serde(rename_all = "camelCase")]
330pub struct PutOrderBulkParams {
331    /// Array of order amendment parameters.
332    pub orders: Vec<PutOrderParams>,
333}
334
335/// Parameters for the POST /position/leverage endpoint.
336#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
337#[builder(default)]
338#[builder(setter(into, strip_option))]
339#[serde(rename_all = "camelCase")]
340pub struct PostPositionLeverageParams {
341    /// Symbol to set leverage for.
342    pub symbol: String,
343    /// Leverage value (0.01 to 100).
344    pub leverage: f64,
345    /// Optional leverage for long position (isolated margin only).
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub target_account_id: Option<i64>,
348}
349
350/// Parameters for the GET /position endpoint.
351#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
352#[builder(default)]
353#[builder(setter(into, strip_option))]
354#[serde(rename_all = "camelCase")]
355pub struct GetPositionParams {
356    /// Generic table filter. Send JSON key/value pairs, such as `{"key": "value"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://www.bitmex.com/app/restAPI#Timestamp-Filters) for more details.
357    #[serde(
358        skip_serializing_if = "Option::is_none",
359        serialize_with = "serialize_json_as_string"
360    )]
361    pub filter: Option<Value>,
362    /// Array of column names to fetch. If omitted, will return all columns.  Note that this method will always return item keys, even when not specified, so you may receive more columns that you expect.
363    #[serde(
364        skip_serializing_if = "Option::is_none",
365        serialize_with = "serialize_json_as_string"
366    )]
367    pub columns: Option<Value>,
368    /// Number of results to fetch.
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub count: Option<i32>,
371}