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}