1use crate::client::RequestParts;
4use crate::ids::{AccountID, InstrumentName, TradeID, TradeSpecifier, TransactionID};
5use crate::models::{
6 ClientExtensions, GuaranteedStopLossDetails, GuaranteedStopLossOrderRejectTransaction,
7 GuaranteedStopLossOrderTransaction, MarketOrderRejectTransaction, MarketOrderTransaction,
8 OrderCancelRejectTransaction, OrderCancelTransaction, OrderFillTransaction, StopLossDetails,
9 StopLossOrderRejectTransaction, StopLossOrderTransaction, TakeProfitDetails,
10 TakeProfitOrderRejectTransaction, TakeProfitOrderTransaction, Trade,
11 TradeClientExtensionsModifyRejectTransaction, TradeClientExtensionsModifyTransaction,
12 TradeStateFilter, TrailingStopLossDetails, TrailingStopLossOrderRejectTransaction,
13 TrailingStopLossOrderTransaction,
14};
15use crate::validation::Validate;
16use crate::{Client, Error, Result};
17use reqwest::Method;
18use serde::{Deserialize, Serialize};
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
24#[serde(rename_all = "camelCase")]
25pub struct ListTradesQuery {
26 #[serde(rename = "ids", default, skip_serializing_if = "Option::is_none")]
28 pub ids: Option<Vec<TradeID>>,
29 #[serde(rename = "state", default, skip_serializing_if = "Option::is_none")]
31 pub state: Option<TradeStateFilter>,
32 #[serde(
34 rename = "instrument",
35 default,
36 skip_serializing_if = "Option::is_none"
37 )]
38 pub instrument: Option<InstrumentName>,
39 #[serde(rename = "count", default, skip_serializing_if = "Option::is_none")]
41 pub count: Option<i64>,
42 #[serde(rename = "beforeID", default, skip_serializing_if = "Option::is_none")]
44 pub before_id: Option<TradeID>,
45}
46
47impl Validate for ListTradesQuery {
48 fn validate(&self) -> Result<()> {
49 if let Some(ids) = &self.ids {
50 crate::validation::nonempty(ids, "ids")?;
51 }
52 crate::validation::count(self.count, 500)
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase")]
59pub struct ListTradesResponse {
60 #[serde(rename = "trades", default, skip_serializing_if = "Option::is_none")]
62 pub trades: Option<Vec<Trade>>,
63 #[serde(
65 rename = "lastTransactionID",
66 default,
67 skip_serializing_if = "Option::is_none"
68 )]
69 pub last_transaction_id: Option<TransactionID>,
70}
71
72impl Client {
73 pub async fn list_trades(
81 &self,
82 account_id: &AccountID,
83 query: Option<&ListTradesQuery>,
84 ) -> std::result::Result<
85 crate::ApiResponse<ListTradesResponse>,
86 crate::OperationError<crate::GenericRejection>,
87 > {
88 let account_id_encoded = crate::client::segment(account_id.as_str());
89 let path = format!("/v3/accounts/{account_id_encoded}/trades");
90 let request = RequestParts::new(Method::GET, &path);
91 let request = match query {
92 Some(query) => request.query(query)?,
93 None => request,
94 };
95 self.execute(request).await
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct ListOpenTradesResponse {
103 #[serde(rename = "trades", default, skip_serializing_if = "Option::is_none")]
105 pub trades: Option<Vec<Trade>>,
106 #[serde(
108 rename = "lastTransactionID",
109 default,
110 skip_serializing_if = "Option::is_none"
111 )]
112 pub last_transaction_id: Option<TransactionID>,
113}
114
115impl Client {
116 pub async fn list_open_trades(
124 &self,
125 account_id: &AccountID,
126 ) -> std::result::Result<
127 crate::ApiResponse<ListOpenTradesResponse>,
128 crate::OperationError<crate::GenericRejection>,
129 > {
130 let account_id_encoded = crate::client::segment(account_id.as_str());
131 let path = format!("/v3/accounts/{account_id_encoded}/openTrades");
132 self.execute(RequestParts::new(Method::GET, &path)).await
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct TradeDetailsResponse {
140 #[serde(rename = "trade", default, skip_serializing_if = "Option::is_none")]
142 pub trade: Option<Trade>,
143 #[serde(
145 rename = "lastTransactionID",
146 default,
147 skip_serializing_if = "Option::is_none"
148 )]
149 pub last_transaction_id: Option<TransactionID>,
150}
151
152impl Client {
153 pub async fn trade_details(
161 &self,
162 account_id: &AccountID,
163 trade_specifier: &TradeSpecifier,
164 ) -> std::result::Result<
165 crate::ApiResponse<TradeDetailsResponse>,
166 crate::OperationError<crate::GenericRejection>,
167 > {
168 let account_id_encoded = crate::client::segment(account_id.as_str());
169 let trade_specifier_encoded = crate::client::segment(trade_specifier.as_str());
170 let path = format!("/v3/accounts/{account_id_encoded}/trades/{trade_specifier_encoded}");
171 self.execute(RequestParts::new(Method::GET, &path)).await
172 }
173}
174
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
177#[serde(rename_all = "camelCase")]
178pub struct CloseTradeBody {
179 #[serde(rename = "units", default, skip_serializing_if = "Option::is_none")]
185 pub units: Option<crate::TradeCloseoutUnits>,
186}
187
188impl Validate for CloseTradeBody {
189 fn validate(&self) -> Result<()> {
190 if let Some(units) = &self.units {
191 units.validate()?;
192 }
193 Ok(())
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct CloseTradeResponse {
201 #[serde(
203 rename = "orderCreateTransaction",
204 default,
205 skip_serializing_if = "Option::is_none"
206 )]
207 pub order_create_transaction: Option<MarketOrderTransaction>,
208 #[serde(
210 rename = "orderFillTransaction",
211 default,
212 skip_serializing_if = "Option::is_none"
213 )]
214 pub order_fill_transaction: Option<OrderFillTransaction>,
215 #[serde(
217 rename = "orderCancelTransaction",
218 default,
219 skip_serializing_if = "Option::is_none"
220 )]
221 pub order_cancel_transaction: Option<OrderCancelTransaction>,
222 #[serde(
224 rename = "relatedTransactionIDs",
225 default,
226 skip_serializing_if = "Option::is_none"
227 )]
228 pub related_transaction_ids: Option<Vec<TransactionID>>,
229 #[serde(
231 rename = "lastTransactionID",
232 default,
233 skip_serializing_if = "Option::is_none"
234 )]
235 pub last_transaction_id: Option<TransactionID>,
236}
237
238#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct CloseTradeRejection {
242 #[serde(
244 rename = "orderRejectTransaction",
245 default,
246 skip_serializing_if = "Option::is_none"
247 )]
248 pub order_reject_transaction: Option<MarketOrderRejectTransaction>,
249 #[serde(rename = "errorCode", default, skip_serializing_if = "Option::is_none")]
251 pub error_code: Option<String>,
252 #[serde(rename = "errorMessage")]
254 pub error_message: String,
255 #[serde(
257 rename = "lastTransactionID",
258 default,
259 skip_serializing_if = "Option::is_none"
260 )]
261 pub last_transaction_id: Option<TransactionID>,
262 #[serde(
265 rename = "relatedTransactionIDs",
266 default,
267 skip_serializing_if = "Option::is_none"
268 )]
269 pub related_transaction_ids: Option<Vec<TransactionID>>,
270}
271
272impl Client {
273 pub async fn close_trade(
282 &self,
283 account_id: &AccountID,
284 trade_specifier: &TradeSpecifier,
285 body: &CloseTradeBody,
286 ) -> std::result::Result<
287 crate::ApiResponse<CloseTradeResponse>,
288 crate::OperationError<CloseTradeRejection>,
289 > {
290 let account_id_encoded = crate::client::segment(account_id.as_str());
291 let trade_specifier_encoded = crate::client::segment(trade_specifier.as_str());
292 let path =
293 format!("/v3/accounts/{account_id_encoded}/trades/{trade_specifier_encoded}/close");
294 self.execute(
295 RequestParts::new(Method::PUT, &path)
296 .body(body)?
297 .mutation(crate::client::MutationAdmission::Exit(account_id)),
298 )
299 .await
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct UpdateTradeClientExtensionsBody {
307 #[serde(
310 rename = "clientExtensions",
311 default,
312 skip_serializing_if = "Option::is_none"
313 )]
314 pub client_extensions: Option<ClientExtensions>,
315}
316
317impl Validate for UpdateTradeClientExtensionsBody {
318 fn validate(&self) -> Result<()> {
319 if self.client_extensions.is_none() {
320 return Err(Error::InvalidInput(
321 "mutation body must not be empty".into(),
322 ));
323 }
324 Ok(())
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct UpdateTradeClientExtensionsResponse {
332 #[serde(
334 rename = "tradeClientExtensionsModifyTransaction",
335 default,
336 skip_serializing_if = "Option::is_none"
337 )]
338 pub trade_client_extensions_modify_transaction: Option<TradeClientExtensionsModifyTransaction>,
339 #[serde(
341 rename = "relatedTransactionIDs",
342 default,
343 skip_serializing_if = "Option::is_none"
344 )]
345 pub related_transaction_ids: Option<Vec<TransactionID>>,
346 #[serde(
348 rename = "lastTransactionID",
349 default,
350 skip_serializing_if = "Option::is_none"
351 )]
352 pub last_transaction_id: Option<TransactionID>,
353}
354
355#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
357#[serde(rename_all = "camelCase")]
358pub struct UpdateTradeClientExtensionsRejection {
359 #[serde(
361 rename = "tradeClientExtensionsModifyRejectTransaction",
362 default,
363 skip_serializing_if = "Option::is_none"
364 )]
365 pub trade_client_extensions_modify_reject_transaction:
366 Option<TradeClientExtensionsModifyRejectTransaction>,
367 #[serde(
369 rename = "lastTransactionID",
370 default,
371 skip_serializing_if = "Option::is_none"
372 )]
373 pub last_transaction_id: Option<TransactionID>,
374 #[serde(
376 rename = "relatedTransactionIDs",
377 default,
378 skip_serializing_if = "Option::is_none"
379 )]
380 pub related_transaction_ids: Option<Vec<TransactionID>>,
381 #[serde(rename = "errorCode", default, skip_serializing_if = "Option::is_none")]
383 pub error_code: Option<String>,
384 #[serde(rename = "errorMessage")]
386 pub error_message: String,
387}
388
389impl Client {
390 pub async fn update_trade_client_extensions(
399 &self,
400 account_id: &AccountID,
401 trade_specifier: &TradeSpecifier,
402 body: &UpdateTradeClientExtensionsBody,
403 ) -> std::result::Result<
404 crate::ApiResponse<UpdateTradeClientExtensionsResponse>,
405 crate::OperationError<UpdateTradeClientExtensionsRejection>,
406 > {
407 let account_id_encoded = crate::client::segment(account_id.as_str());
408 let trade_specifier_encoded = crate::client::segment(trade_specifier.as_str());
409 let path = format!(
410 "/v3/accounts/{account_id_encoded}/trades/{trade_specifier_encoded}/clientExtensions"
411 );
412 self.execute(
413 RequestParts::new(Method::PUT, &path)
414 .body(body)?
415 .mutation(crate::client::MutationAdmission::Ordinary(account_id)),
416 )
417 .await
418 }
419}
420
421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
423#[serde(rename_all = "camelCase")]
424pub struct SetTradeDependentOrdersBody {
425 #[serde(
430 rename = "takeProfit",
431 default,
432 skip_serializing_if = "crate::Patch::is_unchanged"
433 )]
434 pub take_profit: crate::Patch<TakeProfitDetails>,
435 #[serde(
440 rename = "stopLoss",
441 default,
442 skip_serializing_if = "crate::Patch::is_unchanged"
443 )]
444 pub stop_loss: crate::Patch<StopLossDetails>,
445 #[serde(
451 rename = "trailingStopLoss",
452 default,
453 skip_serializing_if = "crate::Patch::is_unchanged"
454 )]
455 pub trailing_stop_loss: crate::Patch<TrailingStopLossDetails>,
456 #[serde(
462 rename = "guaranteedStopLoss",
463 default,
464 skip_serializing_if = "crate::Patch::is_unchanged"
465 )]
466 pub guaranteed_stop_loss: crate::Patch<GuaranteedStopLossDetails>,
467}
468
469impl Validate for SetTradeDependentOrdersBody {
470 fn validate(&self) -> Result<()> {
471 if self.take_profit.is_unchanged()
472 && self.stop_loss.is_unchanged()
473 && self.trailing_stop_loss.is_unchanged()
474 && self.guaranteed_stop_loss.is_unchanged()
475 {
476 return Err(Error::InvalidInput(
477 "mutation body must not be empty".into(),
478 ));
479 }
480 if let crate::Patch::Set(details) = &self.take_profit {
481 details.validate()?;
482 }
483 if let crate::Patch::Set(details) = &self.stop_loss {
484 details.validate()?;
485 }
486 if let crate::Patch::Set(details) = &self.trailing_stop_loss {
487 details.validate()?;
488 }
489 if let crate::Patch::Set(details) = &self.guaranteed_stop_loss {
490 details.validate()?;
491 }
492 Ok(())
493 }
494}
495
496#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
498#[serde(rename_all = "camelCase")]
499pub struct SetTradeDependentOrdersResponse {
500 #[serde(
502 rename = "takeProfitOrderCancelTransaction",
503 default,
504 skip_serializing_if = "Option::is_none"
505 )]
506 pub take_profit_order_cancel_transaction: Option<OrderCancelTransaction>,
507 #[serde(
509 rename = "takeProfitOrderTransaction",
510 default,
511 skip_serializing_if = "Option::is_none"
512 )]
513 pub take_profit_order_transaction: Option<TakeProfitOrderTransaction>,
514 #[serde(
517 rename = "takeProfitOrderFillTransaction",
518 default,
519 skip_serializing_if = "Option::is_none"
520 )]
521 pub take_profit_order_fill_transaction: Option<OrderFillTransaction>,
522 #[serde(
525 rename = "takeProfitOrderCreatedCancelTransaction",
526 default,
527 skip_serializing_if = "Option::is_none"
528 )]
529 pub take_profit_order_created_cancel_transaction: Option<OrderCancelTransaction>,
530 #[serde(
532 rename = "stopLossOrderCancelTransaction",
533 default,
534 skip_serializing_if = "Option::is_none"
535 )]
536 pub stop_loss_order_cancel_transaction: Option<OrderCancelTransaction>,
537 #[serde(
539 rename = "stopLossOrderTransaction",
540 default,
541 skip_serializing_if = "Option::is_none"
542 )]
543 pub stop_loss_order_transaction: Option<StopLossOrderTransaction>,
544 #[serde(
547 rename = "stopLossOrderFillTransaction",
548 default,
549 skip_serializing_if = "Option::is_none"
550 )]
551 pub stop_loss_order_fill_transaction: Option<OrderFillTransaction>,
552 #[serde(
555 rename = "stopLossOrderCreatedCancelTransaction",
556 default,
557 skip_serializing_if = "Option::is_none"
558 )]
559 pub stop_loss_order_created_cancel_transaction: Option<OrderCancelTransaction>,
560 #[serde(
562 rename = "trailingStopLossOrderCancelTransaction",
563 default,
564 skip_serializing_if = "Option::is_none"
565 )]
566 pub trailing_stop_loss_order_cancel_transaction: Option<OrderCancelTransaction>,
567 #[serde(
569 rename = "trailingStopLossOrderTransaction",
570 default,
571 skip_serializing_if = "Option::is_none"
572 )]
573 pub trailing_stop_loss_order_transaction: Option<TrailingStopLossOrderTransaction>,
574 #[serde(
576 rename = "guaranteedStopLossOrderCancelTransaction",
577 default,
578 skip_serializing_if = "Option::is_none"
579 )]
580 pub guaranteed_stop_loss_order_cancel_transaction: Option<OrderCancelTransaction>,
581 #[serde(
583 rename = "guaranteedStopLossOrderTransaction",
584 default,
585 skip_serializing_if = "Option::is_none"
586 )]
587 pub guaranteed_stop_loss_order_transaction: Option<GuaranteedStopLossOrderTransaction>,
588 #[serde(
590 rename = "relatedTransactionIDs",
591 default,
592 skip_serializing_if = "Option::is_none"
593 )]
594 pub related_transaction_ids: Option<Vec<TransactionID>>,
595 #[serde(
597 rename = "lastTransactionID",
598 default,
599 skip_serializing_if = "Option::is_none"
600 )]
601 pub last_transaction_id: Option<TransactionID>,
602}
603
604#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
606#[serde(rename_all = "camelCase")]
607pub struct SetTradeDependentOrdersRejection {
608 #[serde(
611 rename = "takeProfitOrderCancelRejectTransaction",
612 default,
613 skip_serializing_if = "Option::is_none"
614 )]
615 pub take_profit_order_cancel_reject_transaction: Option<OrderCancelRejectTransaction>,
616 #[serde(
618 rename = "takeProfitOrderRejectTransaction",
619 default,
620 skip_serializing_if = "Option::is_none"
621 )]
622 pub take_profit_order_reject_transaction: Option<TakeProfitOrderRejectTransaction>,
623 #[serde(
626 rename = "stopLossOrderCancelRejectTransaction",
627 default,
628 skip_serializing_if = "Option::is_none"
629 )]
630 pub stop_loss_order_cancel_reject_transaction: Option<OrderCancelRejectTransaction>,
631 #[serde(
633 rename = "stopLossOrderRejectTransaction",
634 default,
635 skip_serializing_if = "Option::is_none"
636 )]
637 pub stop_loss_order_reject_transaction: Option<StopLossOrderRejectTransaction>,
638 #[serde(
641 rename = "trailingStopLossOrderCancelRejectTransaction",
642 default,
643 skip_serializing_if = "Option::is_none"
644 )]
645 pub trailing_stop_loss_order_cancel_reject_transaction: Option<OrderCancelRejectTransaction>,
646 #[serde(
649 rename = "trailingStopLossOrderRejectTransaction",
650 default,
651 skip_serializing_if = "Option::is_none"
652 )]
653 pub trailing_stop_loss_order_reject_transaction: Option<TrailingStopLossOrderRejectTransaction>,
654 #[serde(
657 rename = "guaranteedStopLossOrderCancelRejectTransaction",
658 default,
659 skip_serializing_if = "Option::is_none"
660 )]
661 pub guaranteed_stop_loss_order_cancel_reject_transaction: Option<OrderCancelRejectTransaction>,
662 #[serde(
665 rename = "guaranteedStopLossOrderRejectTransaction",
666 default,
667 skip_serializing_if = "Option::is_none"
668 )]
669 pub guaranteed_stop_loss_order_reject_transaction:
670 Option<GuaranteedStopLossOrderRejectTransaction>,
671 #[serde(
673 rename = "lastTransactionID",
674 default,
675 skip_serializing_if = "Option::is_none"
676 )]
677 pub last_transaction_id: Option<TransactionID>,
678 #[serde(
680 rename = "relatedTransactionIDs",
681 default,
682 skip_serializing_if = "Option::is_none"
683 )]
684 pub related_transaction_ids: Option<Vec<TransactionID>>,
685 #[serde(rename = "errorCode", default, skip_serializing_if = "Option::is_none")]
687 pub error_code: Option<String>,
688 #[serde(rename = "errorMessage")]
690 pub error_message: String,
691}
692
693impl Client {
694 pub async fn set_trade_dependent_orders(
703 &self,
704 account_id: &AccountID,
705 trade_specifier: &TradeSpecifier,
706 body: &SetTradeDependentOrdersBody,
707 ) -> std::result::Result<
708 crate::ApiResponse<SetTradeDependentOrdersResponse>,
709 crate::OperationError<SetTradeDependentOrdersRejection>,
710 > {
711 let account_id_encoded = crate::client::segment(account_id.as_str());
712 let trade_specifier_encoded = crate::client::segment(trade_specifier.as_str());
713 let path =
714 format!("/v3/accounts/{account_id_encoded}/trades/{trade_specifier_encoded}/orders");
715 self.execute(
716 RequestParts::new(Method::PUT, &path)
717 .body(body)?
718 .mutation(crate::client::MutationAdmission::Ordinary(account_id)),
719 )
720 .await
721 }
722}