1use crate::client::RequestParts;
4use crate::ids::{AccountID, InstrumentName, TransactionID};
5use crate::models::{
6 ClientExtensions, MarketOrderRejectTransaction, MarketOrderTransaction, OrderCancelTransaction,
7 OrderFillTransaction, Position,
8};
9use crate::validation::Validate;
10use crate::{Client, Error, Result};
11use reqwest::Method;
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct ListPositionsResponse {
18 #[serde(rename = "positions", default, skip_serializing_if = "Option::is_none")]
20 pub positions: Option<Vec<Position>>,
21 #[serde(
23 rename = "lastTransactionID",
24 default,
25 skip_serializing_if = "Option::is_none"
26 )]
27 pub last_transaction_id: Option<TransactionID>,
28}
29
30impl Client {
31 pub async fn list_positions(
39 &self,
40 account_id: &AccountID,
41 ) -> std::result::Result<
42 crate::ApiResponse<ListPositionsResponse>,
43 crate::OperationError<crate::GenericRejection>,
44 > {
45 let account_id_encoded = crate::client::segment(account_id.as_str());
46 let path = format!("/v3/accounts/{account_id_encoded}/positions");
47 self.execute(RequestParts::new(Method::GET, &path)).await
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct ListOpenPositionsResponse {
55 #[serde(rename = "positions", default, skip_serializing_if = "Option::is_none")]
57 pub positions: Option<Vec<Position>>,
58 #[serde(
60 rename = "lastTransactionID",
61 default,
62 skip_serializing_if = "Option::is_none"
63 )]
64 pub last_transaction_id: Option<TransactionID>,
65}
66
67impl Client {
68 pub async fn list_open_positions(
76 &self,
77 account_id: &AccountID,
78 ) -> std::result::Result<
79 crate::ApiResponse<ListOpenPositionsResponse>,
80 crate::OperationError<crate::GenericRejection>,
81 > {
82 let account_id_encoded = crate::client::segment(account_id.as_str());
83 let path = format!("/v3/accounts/{account_id_encoded}/openPositions");
84 self.execute(RequestParts::new(Method::GET, &path)).await
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct PositionDetailsResponse {
92 #[serde(rename = "position", default, skip_serializing_if = "Option::is_none")]
94 pub position: Option<Position>,
95 #[serde(
97 rename = "lastTransactionID",
98 default,
99 skip_serializing_if = "Option::is_none"
100 )]
101 pub last_transaction_id: Option<TransactionID>,
102}
103
104impl Client {
105 pub async fn position_details(
113 &self,
114 account_id: &AccountID,
115 instrument: &InstrumentName,
116 ) -> std::result::Result<
117 crate::ApiResponse<PositionDetailsResponse>,
118 crate::OperationError<crate::GenericRejection>,
119 > {
120 let account_id_encoded = crate::client::segment(account_id.as_str());
121 let instrument_encoded = crate::client::segment(instrument.as_str());
122 let path = format!("/v3/accounts/{account_id_encoded}/positions/{instrument_encoded}");
123 self.execute(RequestParts::new(Method::GET, &path)).await
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130pub struct ClosePositionBody {
131 #[serde(rename = "longUnits", default, skip_serializing_if = "Option::is_none")]
136 pub long_units: Option<crate::PositionCloseoutUnits>,
137 #[serde(
139 rename = "longClientExtensions",
140 default,
141 skip_serializing_if = "Option::is_none"
142 )]
143 pub long_client_extensions: Option<ClientExtensions>,
144 #[serde(
149 rename = "shortUnits",
150 default,
151 skip_serializing_if = "Option::is_none"
152 )]
153 pub short_units: Option<crate::PositionCloseoutUnits>,
154 #[serde(
156 rename = "shortClientExtensions",
157 default,
158 skip_serializing_if = "Option::is_none"
159 )]
160 pub short_client_extensions: Option<ClientExtensions>,
161}
162
163impl Validate for ClosePositionBody {
164 fn validate(&self) -> Result<()> {
165 if self.long_units == Some(crate::PositionCloseoutUnits::None)
166 && self.short_units == Some(crate::PositionCloseoutUnits::None)
167 {
168 return Err(Error::InvalidInput(
169 "position close requests no action".into(),
170 ));
171 }
172 for units in [self.long_units, self.short_units].into_iter().flatten() {
173 units.validate()?;
174 }
175 Ok(())
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct ClosePositionResponse {
183 #[serde(
185 rename = "longOrderCreateTransaction",
186 default,
187 skip_serializing_if = "Option::is_none"
188 )]
189 pub long_order_create_transaction: Option<MarketOrderTransaction>,
190 #[serde(
192 rename = "longOrderFillTransaction",
193 default,
194 skip_serializing_if = "Option::is_none"
195 )]
196 pub long_order_fill_transaction: Option<OrderFillTransaction>,
197 #[serde(
199 rename = "longOrderCancelTransaction",
200 default,
201 skip_serializing_if = "Option::is_none"
202 )]
203 pub long_order_cancel_transaction: Option<OrderCancelTransaction>,
204 #[serde(
206 rename = "shortOrderCreateTransaction",
207 default,
208 skip_serializing_if = "Option::is_none"
209 )]
210 pub short_order_create_transaction: Option<MarketOrderTransaction>,
211 #[serde(
213 rename = "shortOrderFillTransaction",
214 default,
215 skip_serializing_if = "Option::is_none"
216 )]
217 pub short_order_fill_transaction: Option<OrderFillTransaction>,
218 #[serde(
220 rename = "shortOrderCancelTransaction",
221 default,
222 skip_serializing_if = "Option::is_none"
223 )]
224 pub short_order_cancel_transaction: Option<OrderCancelTransaction>,
225 #[serde(
227 rename = "relatedTransactionIDs",
228 default,
229 skip_serializing_if = "Option::is_none"
230 )]
231 pub related_transaction_ids: Option<Vec<TransactionID>>,
232 #[serde(
234 rename = "lastTransactionID",
235 default,
236 skip_serializing_if = "Option::is_none"
237 )]
238 pub last_transaction_id: Option<TransactionID>,
239}
240
241#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
243#[serde(rename_all = "camelCase")]
244pub struct ClosePositionRejection {
245 #[serde(
247 rename = "longOrderRejectTransaction",
248 default,
249 skip_serializing_if = "Option::is_none"
250 )]
251 pub long_order_reject_transaction: Option<MarketOrderRejectTransaction>,
252 #[serde(
254 rename = "shortOrderRejectTransaction",
255 default,
256 skip_serializing_if = "Option::is_none"
257 )]
258 pub short_order_reject_transaction: Option<MarketOrderRejectTransaction>,
259 #[serde(
261 rename = "relatedTransactionIDs",
262 default,
263 skip_serializing_if = "Option::is_none"
264 )]
265 pub related_transaction_ids: Option<Vec<TransactionID>>,
266 #[serde(
268 rename = "lastTransactionID",
269 default,
270 skip_serializing_if = "Option::is_none"
271 )]
272 pub last_transaction_id: Option<TransactionID>,
273 #[serde(rename = "errorCode", default, skip_serializing_if = "Option::is_none")]
275 pub error_code: Option<String>,
276 #[serde(rename = "errorMessage")]
278 pub error_message: String,
279}
280
281impl Client {
282 pub async fn close_position(
291 &self,
292 account_id: &AccountID,
293 instrument: &InstrumentName,
294 body: &ClosePositionBody,
295 ) -> std::result::Result<
296 crate::ApiResponse<ClosePositionResponse>,
297 crate::OperationError<ClosePositionRejection>,
298 > {
299 let account_id_encoded = crate::client::segment(account_id.as_str());
300 let instrument_encoded = crate::client::segment(instrument.as_str());
301 let path =
302 format!("/v3/accounts/{account_id_encoded}/positions/{instrument_encoded}/close");
303 self.execute(
304 RequestParts::new(Method::PUT, &path)
305 .body(body)?
306 .mutation(crate::client::MutationAdmission::Exit(account_id)),
307 )
308 .await
309 }
310}