nautilus_kraken/http/spot/
query.rs1use derive_builder::Builder;
19use serde::{Deserialize, Serialize};
20use ustr::Ustr;
21
22use crate::common::enums::{KrakenOrderSide, KrakenOrderType};
23
24#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
29#[builder(setter(into, strip_option), build_fn(validate = "Self::validate"))]
30pub struct KrakenSpotAddOrderParams {
31 pub pair: Ustr,
33
34 #[serde(rename = "type")]
36 pub side: KrakenOrderSide,
37
38 #[serde(rename = "ordertype")]
40 pub order_type: KrakenOrderType,
41
42 pub volume: String,
44
45 #[builder(default)]
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub price: Option<String>,
49
50 #[builder(default)]
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub price2: Option<String>,
54
55 #[builder(default)]
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub cl_ord_id: Option<String>,
59
60 #[builder(default)]
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub oflags: Option<String>,
64
65 #[builder(default)]
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub timeinforce: Option<String>,
69
70 #[builder(default)]
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub expiretm: Option<String>,
74
75 #[builder(default)]
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub broker: Option<Ustr>,
79}
80
81impl KrakenSpotAddOrderParamsBuilder {
82 fn validate(&self) -> Result<(), String> {
83 if let Some(
85 KrakenOrderType::Limit
86 | KrakenOrderType::StopLossLimit
87 | KrakenOrderType::TakeProfitLimit,
88 ) = self.order_type
89 && (self.price.is_none() || self.price.as_ref().unwrap().is_none())
90 {
91 return Err("price is required for limit orders".to_string());
92 }
93
94 if let Some(KrakenOrderType::StopLossLimit | KrakenOrderType::TakeProfitLimit) =
96 self.order_type
97 && (self.price2.is_none() || self.price2.as_ref().unwrap().is_none())
98 {
99 return Err(
100 "price2 (limit price) is required for stop-loss-limit and take-profit-limit orders"
101 .to_string(),
102 );
103 }
104 Ok(())
105 }
106}
107
108#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
113#[builder(setter(into, strip_option))]
114pub struct KrakenSpotCancelOrderParams {
115 #[builder(default)]
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub txid: Option<String>,
120
121 #[builder(default)]
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub cl_ord_id: Option<String>,
125}
126
127#[derive(Clone, Debug, Serialize, Deserialize)]
132pub struct KrakenSpotCancelOrderBatchParams {
133 pub orders: Vec<String>,
136}
137
138#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
146#[builder(setter(into, strip_option))]
147pub struct KrakenSpotEditOrderParams {
148 pub pair: Ustr,
150
151 #[builder(default)]
153 #[serde(skip_serializing_if = "Option::is_none")]
154 pub txid: Option<String>,
155
156 #[builder(default)]
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub cl_ord_id: Option<String>,
160
161 #[builder(default)]
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub volume: Option<String>,
165
166 #[builder(default)]
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub price: Option<String>,
170
171 #[builder(default)]
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub price2: Option<String>,
175}
176
177#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
185#[builder(setter(into, strip_option))]
186pub struct KrakenSpotAmendOrderParams {
187 #[builder(default)]
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub txid: Option<String>,
191
192 #[builder(default)]
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub cl_ord_id: Option<String>,
196
197 #[builder(default)]
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub order_qty: Option<String>,
201
202 #[builder(default)]
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub limit_price: Option<String>,
206
207 #[builder(default)]
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub trigger_price: Option<String>,
211}
212
213#[cfg(test)]
214mod tests {
215 use rstest::rstest;
216
217 use super::*;
218
219 #[rstest]
220 fn test_add_order_params_builder() {
221 let params = KrakenSpotAddOrderParamsBuilder::default()
222 .pair("XXBTZUSD")
223 .side(KrakenOrderSide::Buy)
224 .order_type(KrakenOrderType::Limit)
225 .volume("0.01")
226 .price("50000.0")
227 .cl_ord_id("my-order-123")
228 .broker("test-broker")
229 .build()
230 .unwrap();
231
232 assert_eq!(params.pair, Ustr::from("XXBTZUSD"));
233 assert_eq!(params.side, KrakenOrderSide::Buy);
234 assert_eq!(params.order_type, KrakenOrderType::Limit);
235 assert_eq!(params.volume, "0.01");
236 assert_eq!(params.price, Some("50000.0".to_string()));
237 assert_eq!(params.cl_ord_id, Some("my-order-123".to_string()));
238 assert_eq!(params.broker, Some(Ustr::from("test-broker")));
239 }
240
241 #[rstest]
242 fn test_add_order_params_serialization() {
243 let params = KrakenSpotAddOrderParamsBuilder::default()
244 .pair("XXBTZUSD")
245 .side(KrakenOrderSide::Buy)
246 .order_type(KrakenOrderType::Market)
247 .volume("0.01")
248 .broker("broker-id")
249 .build()
250 .unwrap();
251
252 let encoded = serde_urlencoded::to_string(¶ms).unwrap();
253
254 assert!(encoded.contains("pair=XXBTZUSD"));
255 assert!(encoded.contains("type=buy"));
256 assert!(encoded.contains("ordertype=market"));
257 assert!(encoded.contains("volume=0.01"));
258 assert!(encoded.contains("broker=broker-id"));
259 assert!(!encoded.contains("price="));
260 }
261
262 #[rstest]
263 fn test_add_order_params_limit_requires_price() {
264 let result = KrakenSpotAddOrderParamsBuilder::default()
265 .pair("XXBTZUSD")
266 .side(KrakenOrderSide::Buy)
267 .order_type(KrakenOrderType::Limit)
268 .volume("0.01")
269 .build();
270
271 assert!(result.is_err());
272 assert!(
273 result
274 .unwrap_err()
275 .to_string()
276 .contains("price is required")
277 );
278 }
279
280 #[rstest]
281 fn test_cancel_order_params_builder() {
282 let params = KrakenSpotCancelOrderParamsBuilder::default()
283 .txid("TXID123")
284 .build()
285 .unwrap();
286
287 assert_eq!(params.txid, Some("TXID123".to_string()));
288 assert_eq!(params.cl_ord_id, None);
289 }
290
291 #[rstest]
292 fn test_cancel_order_params_serialization() {
293 let params = KrakenSpotCancelOrderParamsBuilder::default()
294 .cl_ord_id("my-order")
295 .build()
296 .unwrap();
297
298 let encoded = serde_urlencoded::to_string(¶ms).unwrap();
299
300 assert!(encoded.contains("cl_ord_id=my-order"));
301 assert!(!encoded.contains("txid="));
302 }
303}