use crate::client::RequestParts;
use crate::ids::{AccountID, InstrumentName, TradeID, TradeSpecifier, TransactionID};
use crate::models::{
ClientExtensions, GuaranteedStopLossDetails, GuaranteedStopLossOrderRejectTransaction,
GuaranteedStopLossOrderTransaction, MarketOrderRejectTransaction, MarketOrderTransaction,
OrderCancelRejectTransaction, OrderCancelTransaction, OrderFillTransaction, StopLossDetails,
StopLossOrderRejectTransaction, StopLossOrderTransaction, TakeProfitDetails,
TakeProfitOrderRejectTransaction, TakeProfitOrderTransaction, Trade,
TradeClientExtensionsModifyRejectTransaction, TradeClientExtensionsModifyTransaction,
TradeStateFilter, TrailingStopLossDetails, TrailingStopLossOrderRejectTransaction,
TrailingStopLossOrderTransaction,
};
use crate::validation::Validate;
use crate::{Client, Error, Result};
use reqwest::Method;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ListTradesQuery {
#[serde(rename = "ids", default, skip_serializing_if = "Option::is_none")]
pub ids: Option<Vec<TradeID>>,
#[serde(rename = "state", default, skip_serializing_if = "Option::is_none")]
pub state: Option<TradeStateFilter>,
#[serde(
rename = "instrument",
default,
skip_serializing_if = "Option::is_none"
)]
pub instrument: Option<InstrumentName>,
#[serde(rename = "count", default, skip_serializing_if = "Option::is_none")]
pub count: Option<i64>,
#[serde(rename = "beforeID", default, skip_serializing_if = "Option::is_none")]
pub before_id: Option<TradeID>,
}
impl Validate for ListTradesQuery {
fn validate(&self) -> Result<()> {
if let Some(ids) = &self.ids {
crate::validation::nonempty(ids, "ids")?;
}
crate::validation::count(self.count, 500)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListTradesResponse {
#[serde(rename = "trades", default, skip_serializing_if = "Option::is_none")]
pub trades: Option<Vec<Trade>>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
impl Client {
pub async fn list_trades(
&self,
account_id: &AccountID,
query: Option<&ListTradesQuery>,
) -> std::result::Result<
crate::ApiResponse<ListTradesResponse>,
crate::OperationError<crate::GenericRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let path = format!("/v3/accounts/{account_id_encoded}/trades");
let request = RequestParts::new(Method::GET, &path);
let request = match query {
Some(query) => request.query(query)?,
None => request,
};
self.execute(request).await
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListOpenTradesResponse {
#[serde(rename = "trades", default, skip_serializing_if = "Option::is_none")]
pub trades: Option<Vec<Trade>>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
impl Client {
pub async fn list_open_trades(
&self,
account_id: &AccountID,
) -> std::result::Result<
crate::ApiResponse<ListOpenTradesResponse>,
crate::OperationError<crate::GenericRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let path = format!("/v3/accounts/{account_id_encoded}/openTrades");
self.execute(RequestParts::new(Method::GET, &path)).await
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TradeDetailsResponse {
#[serde(rename = "trade", default, skip_serializing_if = "Option::is_none")]
pub trade: Option<Trade>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
impl Client {
pub async fn trade_details(
&self,
account_id: &AccountID,
trade_specifier: &TradeSpecifier,
) -> std::result::Result<
crate::ApiResponse<TradeDetailsResponse>,
crate::OperationError<crate::GenericRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let trade_specifier_encoded = crate::client::segment(trade_specifier.as_str());
let path = format!("/v3/accounts/{account_id_encoded}/trades/{trade_specifier_encoded}");
self.execute(RequestParts::new(Method::GET, &path)).await
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CloseTradeBody {
#[serde(rename = "units", default, skip_serializing_if = "Option::is_none")]
pub units: Option<crate::TradeCloseoutUnits>,
}
impl Validate for CloseTradeBody {
fn validate(&self) -> Result<()> {
if let Some(units) = &self.units {
units.validate()?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CloseTradeResponse {
#[serde(
rename = "orderCreateTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub order_create_transaction: Option<MarketOrderTransaction>,
#[serde(
rename = "orderFillTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub order_fill_transaction: Option<OrderFillTransaction>,
#[serde(
rename = "orderCancelTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub order_cancel_transaction: Option<OrderCancelTransaction>,
#[serde(
rename = "relatedTransactionIDs",
default,
skip_serializing_if = "Option::is_none"
)]
pub related_transaction_ids: Option<Vec<TransactionID>>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CloseTradeRejection {
#[serde(
rename = "orderRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub order_reject_transaction: Option<MarketOrderRejectTransaction>,
#[serde(rename = "errorCode", default, skip_serializing_if = "Option::is_none")]
pub error_code: Option<String>,
#[serde(rename = "errorMessage")]
pub error_message: String,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
#[serde(
rename = "relatedTransactionIDs",
default,
skip_serializing_if = "Option::is_none"
)]
pub related_transaction_ids: Option<Vec<TransactionID>>,
}
impl Client {
pub async fn close_trade(
&self,
account_id: &AccountID,
trade_specifier: &TradeSpecifier,
body: &CloseTradeBody,
) -> std::result::Result<
crate::ApiResponse<CloseTradeResponse>,
crate::OperationError<CloseTradeRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let trade_specifier_encoded = crate::client::segment(trade_specifier.as_str());
let path =
format!("/v3/accounts/{account_id_encoded}/trades/{trade_specifier_encoded}/close");
self.execute(
RequestParts::new(Method::PUT, &path)
.body(body)?
.mutation(crate::client::MutationAdmission::Exit(account_id)),
)
.await
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateTradeClientExtensionsBody {
#[serde(
rename = "clientExtensions",
default,
skip_serializing_if = "Option::is_none"
)]
pub client_extensions: Option<ClientExtensions>,
}
impl Validate for UpdateTradeClientExtensionsBody {
fn validate(&self) -> Result<()> {
if self.client_extensions.is_none() {
return Err(Error::InvalidInput(
"mutation body must not be empty".into(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateTradeClientExtensionsResponse {
#[serde(
rename = "tradeClientExtensionsModifyTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub trade_client_extensions_modify_transaction: Option<TradeClientExtensionsModifyTransaction>,
#[serde(
rename = "relatedTransactionIDs",
default,
skip_serializing_if = "Option::is_none"
)]
pub related_transaction_ids: Option<Vec<TransactionID>>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateTradeClientExtensionsRejection {
#[serde(
rename = "tradeClientExtensionsModifyRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub trade_client_extensions_modify_reject_transaction:
Option<TradeClientExtensionsModifyRejectTransaction>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
#[serde(
rename = "relatedTransactionIDs",
default,
skip_serializing_if = "Option::is_none"
)]
pub related_transaction_ids: Option<Vec<TransactionID>>,
#[serde(rename = "errorCode", default, skip_serializing_if = "Option::is_none")]
pub error_code: Option<String>,
#[serde(rename = "errorMessage")]
pub error_message: String,
}
impl Client {
pub async fn update_trade_client_extensions(
&self,
account_id: &AccountID,
trade_specifier: &TradeSpecifier,
body: &UpdateTradeClientExtensionsBody,
) -> std::result::Result<
crate::ApiResponse<UpdateTradeClientExtensionsResponse>,
crate::OperationError<UpdateTradeClientExtensionsRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let trade_specifier_encoded = crate::client::segment(trade_specifier.as_str());
let path = format!(
"/v3/accounts/{account_id_encoded}/trades/{trade_specifier_encoded}/clientExtensions"
);
self.execute(
RequestParts::new(Method::PUT, &path)
.body(body)?
.mutation(crate::client::MutationAdmission::Ordinary(account_id)),
)
.await
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetTradeDependentOrdersBody {
#[serde(
rename = "takeProfit",
default,
skip_serializing_if = "crate::Patch::is_unchanged"
)]
pub take_profit: crate::Patch<TakeProfitDetails>,
#[serde(
rename = "stopLoss",
default,
skip_serializing_if = "crate::Patch::is_unchanged"
)]
pub stop_loss: crate::Patch<StopLossDetails>,
#[serde(
rename = "trailingStopLoss",
default,
skip_serializing_if = "crate::Patch::is_unchanged"
)]
pub trailing_stop_loss: crate::Patch<TrailingStopLossDetails>,
#[serde(
rename = "guaranteedStopLoss",
default,
skip_serializing_if = "crate::Patch::is_unchanged"
)]
pub guaranteed_stop_loss: crate::Patch<GuaranteedStopLossDetails>,
}
impl Validate for SetTradeDependentOrdersBody {
fn validate(&self) -> Result<()> {
if self.take_profit.is_unchanged()
&& self.stop_loss.is_unchanged()
&& self.trailing_stop_loss.is_unchanged()
&& self.guaranteed_stop_loss.is_unchanged()
{
return Err(Error::InvalidInput(
"mutation body must not be empty".into(),
));
}
if let crate::Patch::Set(details) = &self.take_profit {
details.validate()?;
}
if let crate::Patch::Set(details) = &self.stop_loss {
details.validate()?;
}
if let crate::Patch::Set(details) = &self.trailing_stop_loss {
details.validate()?;
}
if let crate::Patch::Set(details) = &self.guaranteed_stop_loss {
details.validate()?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetTradeDependentOrdersResponse {
#[serde(
rename = "takeProfitOrderCancelTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub take_profit_order_cancel_transaction: Option<OrderCancelTransaction>,
#[serde(
rename = "takeProfitOrderTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub take_profit_order_transaction: Option<TakeProfitOrderTransaction>,
#[serde(
rename = "takeProfitOrderFillTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub take_profit_order_fill_transaction: Option<OrderFillTransaction>,
#[serde(
rename = "takeProfitOrderCreatedCancelTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub take_profit_order_created_cancel_transaction: Option<OrderCancelTransaction>,
#[serde(
rename = "stopLossOrderCancelTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub stop_loss_order_cancel_transaction: Option<OrderCancelTransaction>,
#[serde(
rename = "stopLossOrderTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub stop_loss_order_transaction: Option<StopLossOrderTransaction>,
#[serde(
rename = "stopLossOrderFillTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub stop_loss_order_fill_transaction: Option<OrderFillTransaction>,
#[serde(
rename = "stopLossOrderCreatedCancelTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub stop_loss_order_created_cancel_transaction: Option<OrderCancelTransaction>,
#[serde(
rename = "trailingStopLossOrderCancelTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub trailing_stop_loss_order_cancel_transaction: Option<OrderCancelTransaction>,
#[serde(
rename = "trailingStopLossOrderTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub trailing_stop_loss_order_transaction: Option<TrailingStopLossOrderTransaction>,
#[serde(
rename = "guaranteedStopLossOrderCancelTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub guaranteed_stop_loss_order_cancel_transaction: Option<OrderCancelTransaction>,
#[serde(
rename = "guaranteedStopLossOrderTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub guaranteed_stop_loss_order_transaction: Option<GuaranteedStopLossOrderTransaction>,
#[serde(
rename = "relatedTransactionIDs",
default,
skip_serializing_if = "Option::is_none"
)]
pub related_transaction_ids: Option<Vec<TransactionID>>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetTradeDependentOrdersRejection {
#[serde(
rename = "takeProfitOrderCancelRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub take_profit_order_cancel_reject_transaction: Option<OrderCancelRejectTransaction>,
#[serde(
rename = "takeProfitOrderRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub take_profit_order_reject_transaction: Option<TakeProfitOrderRejectTransaction>,
#[serde(
rename = "stopLossOrderCancelRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub stop_loss_order_cancel_reject_transaction: Option<OrderCancelRejectTransaction>,
#[serde(
rename = "stopLossOrderRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub stop_loss_order_reject_transaction: Option<StopLossOrderRejectTransaction>,
#[serde(
rename = "trailingStopLossOrderCancelRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub trailing_stop_loss_order_cancel_reject_transaction: Option<OrderCancelRejectTransaction>,
#[serde(
rename = "trailingStopLossOrderRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub trailing_stop_loss_order_reject_transaction: Option<TrailingStopLossOrderRejectTransaction>,
#[serde(
rename = "guaranteedStopLossOrderCancelRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub guaranteed_stop_loss_order_cancel_reject_transaction: Option<OrderCancelRejectTransaction>,
#[serde(
rename = "guaranteedStopLossOrderRejectTransaction",
default,
skip_serializing_if = "Option::is_none"
)]
pub guaranteed_stop_loss_order_reject_transaction:
Option<GuaranteedStopLossOrderRejectTransaction>,
#[serde(
rename = "lastTransactionID",
default,
skip_serializing_if = "Option::is_none"
)]
pub last_transaction_id: Option<TransactionID>,
#[serde(
rename = "relatedTransactionIDs",
default,
skip_serializing_if = "Option::is_none"
)]
pub related_transaction_ids: Option<Vec<TransactionID>>,
#[serde(rename = "errorCode", default, skip_serializing_if = "Option::is_none")]
pub error_code: Option<String>,
#[serde(rename = "errorMessage")]
pub error_message: String,
}
impl Client {
pub async fn set_trade_dependent_orders(
&self,
account_id: &AccountID,
trade_specifier: &TradeSpecifier,
body: &SetTradeDependentOrdersBody,
) -> std::result::Result<
crate::ApiResponse<SetTradeDependentOrdersResponse>,
crate::OperationError<SetTradeDependentOrdersRejection>,
> {
let account_id_encoded = crate::client::segment(account_id.as_str());
let trade_specifier_encoded = crate::client::segment(trade_specifier.as_str());
let path =
format!("/v3/accounts/{account_id_encoded}/trades/{trade_specifier_encoded}/orders");
self.execute(
RequestParts::new(Method::PUT, &path)
.body(body)?
.mutation(crate::client::MutationAdmission::Ordinary(account_id)),
)
.await
}
}