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 Ok(())
94 }
95}
96
97#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
102#[builder(setter(into, strip_option))]
103pub struct KrakenSpotCancelOrderParams {
104 #[builder(default)]
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub txid: Option<String>,
109
110 #[builder(default)]
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub cl_ord_id: Option<String>,
114}
115
116#[derive(Clone, Debug, Serialize, Deserialize)]
121pub struct KrakenSpotCancelOrderBatchParams {
122 pub orders: Vec<String>,
125}
126
127#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
135#[builder(setter(into, strip_option))]
136pub struct KrakenSpotEditOrderParams {
137 pub pair: Ustr,
139
140 #[builder(default)]
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub txid: Option<String>,
144
145 #[builder(default)]
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub cl_ord_id: Option<String>,
149
150 #[builder(default)]
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub volume: Option<String>,
154
155 #[builder(default)]
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub price: Option<String>,
159
160 #[builder(default)]
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub price2: Option<String>,
164}
165
166#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
174#[builder(setter(into, strip_option))]
175pub struct KrakenSpotAmendOrderParams {
176 #[builder(default)]
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub txid: Option<String>,
180
181 #[builder(default)]
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub cl_ord_id: Option<String>,
185
186 #[builder(default)]
188 #[serde(skip_serializing_if = "Option::is_none")]
189 pub order_qty: Option<String>,
190
191 #[builder(default)]
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub limit_price: Option<String>,
195
196 #[builder(default)]
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub trigger_price: Option<String>,
200}
201
202#[cfg(test)]
203mod tests {
204 use rstest::rstest;
205
206 use super::*;
207
208 #[rstest]
209 fn test_add_order_params_builder() {
210 let params = KrakenSpotAddOrderParamsBuilder::default()
211 .pair("XXBTZUSD")
212 .side(KrakenOrderSide::Buy)
213 .order_type(KrakenOrderType::Limit)
214 .volume("0.01")
215 .price("50000.0")
216 .cl_ord_id("my-order-123")
217 .broker("test-broker")
218 .build()
219 .unwrap();
220
221 assert_eq!(params.pair, Ustr::from("XXBTZUSD"));
222 assert_eq!(params.side, KrakenOrderSide::Buy);
223 assert_eq!(params.order_type, KrakenOrderType::Limit);
224 assert_eq!(params.volume, "0.01");
225 assert_eq!(params.price, Some("50000.0".to_string()));
226 assert_eq!(params.cl_ord_id, Some("my-order-123".to_string()));
227 assert_eq!(params.broker, Some(Ustr::from("test-broker")));
228 }
229
230 #[rstest]
231 fn test_add_order_params_serialization() {
232 let params = KrakenSpotAddOrderParamsBuilder::default()
233 .pair("XXBTZUSD")
234 .side(KrakenOrderSide::Buy)
235 .order_type(KrakenOrderType::Market)
236 .volume("0.01")
237 .broker("broker-id")
238 .build()
239 .unwrap();
240
241 let encoded = serde_urlencoded::to_string(¶ms).unwrap();
242
243 assert!(encoded.contains("pair=XXBTZUSD"));
244 assert!(encoded.contains("type=buy"));
245 assert!(encoded.contains("ordertype=market"));
246 assert!(encoded.contains("volume=0.01"));
247 assert!(encoded.contains("broker=broker-id"));
248 assert!(!encoded.contains("price="));
249 }
250
251 #[rstest]
252 fn test_add_order_params_limit_requires_price() {
253 let result = KrakenSpotAddOrderParamsBuilder::default()
254 .pair("XXBTZUSD")
255 .side(KrakenOrderSide::Buy)
256 .order_type(KrakenOrderType::Limit)
257 .volume("0.01")
258 .build();
259
260 assert!(result.is_err());
261 assert!(
262 result
263 .unwrap_err()
264 .to_string()
265 .contains("price is required")
266 );
267 }
268
269 #[rstest]
270 fn test_cancel_order_params_builder() {
271 let params = KrakenSpotCancelOrderParamsBuilder::default()
272 .txid("TXID123")
273 .build()
274 .unwrap();
275
276 assert_eq!(params.txid, Some("TXID123".to_string()));
277 assert_eq!(params.cl_ord_id, None);
278 }
279
280 #[rstest]
281 fn test_cancel_order_params_serialization() {
282 let params = KrakenSpotCancelOrderParamsBuilder::default()
283 .cl_ord_id("my-order")
284 .build()
285 .unwrap();
286
287 let encoded = serde_urlencoded::to_string(¶ms).unwrap();
288
289 assert!(encoded.contains("cl_ord_id=my-order"));
290 assert!(!encoded.contains("txid="));
291 }
292}