nautilus_kraken/http/futures/
query.rs1use derive_builder::Builder;
19use serde::{Deserialize, Serialize};
20use ustr::Ustr;
21
22use crate::common::enums::{KrakenFuturesOrderType, KrakenOrderSide, KrakenTriggerSignal};
23
24#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
29#[serde(rename_all = "camelCase")]
30#[builder(setter(into, strip_option), build_fn(validate = "Self::validate"))]
31pub struct KrakenFuturesSendOrderParams {
32 pub symbol: Ustr,
34
35 pub side: KrakenOrderSide,
37
38 pub order_type: KrakenFuturesOrderType,
40
41 pub size: String,
43
44 #[builder(default)]
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub cli_ord_id: Option<String>,
48
49 #[builder(default)]
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub limit_price: Option<String>,
53
54 #[builder(default)]
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub stop_price: Option<String>,
58
59 #[builder(default)]
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub reduce_only: Option<bool>,
63
64 #[builder(default)]
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub trigger_signal: Option<KrakenTriggerSignal>,
68
69 #[builder(default)]
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub trailing_stop_deviation_unit: Option<String>,
73
74 #[builder(default)]
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub trailing_stop_max_deviation: Option<String>,
78
79 #[builder(default)]
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub broker: Option<Ustr>,
83}
84
85impl KrakenFuturesSendOrderParamsBuilder {
86 fn validate(&self) -> Result<(), String> {
87 if let Some(ref order_type) = self.order_type {
89 match order_type {
90 KrakenFuturesOrderType::Limit
91 | KrakenFuturesOrderType::Ioc
92 | KrakenFuturesOrderType::Post => {
93 if self.limit_price.is_none() || self.limit_price.as_ref().unwrap().is_none() {
94 return Err("limit_price is required for limit orders".to_string());
95 }
96 }
97 KrakenFuturesOrderType::Stop | KrakenFuturesOrderType::StopLoss => {
98 if self.stop_price.is_none() || self.stop_price.as_ref().unwrap().is_none() {
99 return Err("stop_price is required for stop orders".to_string());
100 }
101 }
102 _ => {}
103 }
104 }
105 Ok(())
106 }
107}
108
109#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
114#[serde(rename_all = "camelCase")]
115#[builder(setter(into, strip_option))]
116pub struct KrakenFuturesCancelOrderParams {
117 #[builder(default)]
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub order_id: Option<String>,
121
122 #[builder(default)]
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub cli_ord_id: Option<String>,
126}
127
128#[derive(Clone, Debug, Serialize, Deserialize)]
133pub struct KrakenFuturesBatchCancelItem {
134 pub order: String,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub order_id: Option<String>,
140
141 #[serde(rename = "cliOrdId", skip_serializing_if = "Option::is_none")]
143 pub cli_ord_id: Option<String>,
144}
145
146impl KrakenFuturesBatchCancelItem {
147 #[must_use]
149 pub fn from_order_id(order_id: impl Into<String>) -> Self {
150 Self {
151 order: "cancel".to_string(),
152 order_id: Some(order_id.into()),
153 cli_ord_id: None,
154 }
155 }
156
157 #[must_use]
159 pub fn from_client_order_id(cli_ord_id: impl Into<String>) -> Self {
160 Self {
161 order: "cancel".to_string(),
162 order_id: None,
163 cli_ord_id: Some(cli_ord_id.into()),
164 }
165 }
166}
167
168#[derive(Clone, Debug, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct KrakenFuturesBatchOrderParams<T: Serialize> {
178 pub batch_order: Vec<T>,
180}
181
182impl<T: Serialize> KrakenFuturesBatchOrderParams<T> {
183 #[must_use]
185 pub fn new(batch_order: Vec<T>) -> Self {
186 Self { batch_order }
187 }
188
189 pub fn to_body(&self) -> Result<String, serde_json::Error> {
191 let json_str = serde_json::to_string(self)?;
192 Ok(format!("json={json_str}"))
193 }
194}
195
196#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
201#[serde(rename_all = "camelCase")]
202#[builder(setter(into, strip_option))]
203pub struct KrakenFuturesEditOrderParams {
204 #[builder(default)]
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub order_id: Option<String>,
208
209 #[builder(default)]
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub cli_ord_id: Option<String>,
213
214 #[builder(default)]
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub size: Option<String>,
218
219 #[builder(default)]
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub limit_price: Option<String>,
223
224 #[builder(default)]
226 #[serde(skip_serializing_if = "Option::is_none")]
227 pub stop_price: Option<String>,
228}
229
230#[derive(Clone, Debug, Default, Serialize, Deserialize, Builder)]
235#[serde(rename_all = "camelCase")]
236#[builder(setter(into, strip_option), default)]
237pub struct KrakenFuturesCancelAllOrdersParams {
238 #[serde(skip_serializing_if = "Option::is_none")]
240 pub symbol: Option<Ustr>,
241}
242
243#[derive(Clone, Debug, Default, Serialize, Deserialize, Builder)]
248#[serde(rename_all = "camelCase")]
249#[builder(setter(into, strip_option), default)]
250pub struct KrakenFuturesOpenOrdersParams {
251 }
253
254#[derive(Clone, Debug, Default, Serialize, Deserialize, Builder)]
259#[serde(rename_all = "camelCase")]
260#[builder(setter(into, strip_option), default)]
261pub struct KrakenFuturesFillsParams {
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub last_fill_time: Option<String>,
265}
266
267#[derive(Clone, Debug, Default, Serialize, Deserialize, Builder)]
272#[serde(rename_all = "camelCase")]
273#[builder(setter(into, strip_option), default)]
274pub struct KrakenFuturesOpenPositionsParams {
275 }
277
278#[cfg(test)]
279mod tests {
280 use rstest::rstest;
281
282 use super::*;
283
284 #[rstest]
285 fn test_send_order_params_builder() {
286 let params = KrakenFuturesSendOrderParamsBuilder::default()
287 .symbol("PI_XBTUSD")
288 .side(KrakenOrderSide::Buy)
289 .order_type(KrakenFuturesOrderType::Limit)
290 .size("1000")
291 .limit_price("50000.0")
292 .cli_ord_id("test-order-123")
293 .reduce_only(false)
294 .build()
295 .unwrap();
296
297 assert_eq!(params.symbol, Ustr::from("PI_XBTUSD"));
298 assert_eq!(params.side, KrakenOrderSide::Buy);
299 assert_eq!(params.order_type, KrakenFuturesOrderType::Limit);
300 assert_eq!(params.size, "1000");
301 assert_eq!(params.limit_price, Some("50000.0".to_string()));
302 assert_eq!(params.cli_ord_id, Some("test-order-123".to_string()));
303 }
304
305 #[rstest]
306 fn test_send_order_params_serialization() {
307 let params = KrakenFuturesSendOrderParamsBuilder::default()
308 .symbol("PI_XBTUSD")
309 .side(KrakenOrderSide::Buy)
310 .order_type(KrakenFuturesOrderType::Ioc)
311 .size("500")
312 .limit_price("48000.0")
313 .build()
314 .unwrap();
315
316 let json = serde_json::to_string(¶ms).unwrap();
317 assert!(json.contains("\"orderType\":\"ioc\""));
318 assert!(json.contains("\"limitPrice\":\"48000.0\""));
319 }
320
321 #[rstest]
322 fn test_send_order_params_missing_limit_price() {
323 let result = KrakenFuturesSendOrderParamsBuilder::default()
324 .symbol("PI_XBTUSD")
325 .side(KrakenOrderSide::Buy)
326 .order_type(KrakenFuturesOrderType::Limit)
327 .size("1000")
328 .build();
329
330 assert!(result.is_err());
331 assert!(result.unwrap_err().to_string().contains("limit_price"));
332 }
333
334 #[rstest]
335 fn test_cancel_order_params_builder() {
336 let params = KrakenFuturesCancelOrderParamsBuilder::default()
337 .order_id("abc-123")
338 .build()
339 .unwrap();
340
341 assert_eq!(params.order_id, Some("abc-123".to_string()));
342 }
343
344 #[rstest]
345 fn test_edit_order_params_builder() {
346 let params = KrakenFuturesEditOrderParamsBuilder::default()
347 .order_id("abc-123")
348 .size("2000")
349 .limit_price("51000.0")
350 .build()
351 .unwrap();
352
353 assert_eq!(params.order_id, Some("abc-123".to_string()));
354 assert_eq!(params.size, Some("2000".to_string()));
355 assert_eq!(params.limit_price, Some("51000.0".to_string()));
356 }
357}