Skip to main content

oanda_client/
position.rs

1//! OANDA position endpoint contracts.
2// Shared imports vary by capability; unused ones are deliberately allowed.
3#[allow(unused_imports)]
4use crate::Timestamp;
5#[allow(unused_imports)]
6use crate::ids::*;
7#[allow(unused_imports)]
8use crate::models::*;
9#[allow(unused_imports)]
10use crate::{Client, Result};
11#[allow(unused_imports)]
12use reqwest::Method;
13#[allow(unused_imports)]
14use rust_decimal::Decimal;
15#[allow(unused_imports)]
16use serde::{Deserialize, Serialize};
17
18/// Successful response for `list_positions`.
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct ListPositionsResponse {
22    /// The list of Account Positions.
23    #[serde(rename = "positions", default, skip_serializing_if = "Option::is_none")]
24    pub positions: Option<Vec<Position>>,
25    /// The ID of the most recent Transaction created for the Account
26    #[serde(
27        rename = "lastTransactionID",
28        default,
29        skip_serializing_if = "Option::is_none"
30    )]
31    pub last_transaction_id: Option<TransactionID>,
32}
33
34impl Client {
35    /// List positions.
36    pub async fn list_positions(
37        &self,
38        account_id: &AccountID,
39    ) -> std::result::Result<
40        crate::ApiResponse<ListPositionsResponse>,
41        crate::OperationError<crate::GenericRejection>,
42    > {
43        let account_id_encoded = crate::client::segment(account_id.as_str());
44        let path = format!("/v3/accounts/{account_id_encoded}/positions");
45        self.execute(Method::GET, &path, None::<&()>, None::<&()>, None, None)
46            .await
47    }
48}
49
50/// Successful response for `list_open_positions`.
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct ListOpenPositionsResponse {
54    /// The list of open Positions in the Account.
55    #[serde(rename = "positions", default, skip_serializing_if = "Option::is_none")]
56    pub positions: Option<Vec<Position>>,
57    /// The ID of the most recent Transaction created for the Account
58    #[serde(
59        rename = "lastTransactionID",
60        default,
61        skip_serializing_if = "Option::is_none"
62    )]
63    pub last_transaction_id: Option<TransactionID>,
64}
65
66impl Client {
67    /// List open positions.
68    pub async fn list_open_positions(
69        &self,
70        account_id: &AccountID,
71    ) -> std::result::Result<
72        crate::ApiResponse<ListOpenPositionsResponse>,
73        crate::OperationError<crate::GenericRejection>,
74    > {
75        let account_id_encoded = crate::client::segment(account_id.as_str());
76        let path = format!("/v3/accounts/{account_id_encoded}/openPositions");
77        self.execute(Method::GET, &path, None::<&()>, None::<&()>, None, None)
78            .await
79    }
80}
81
82/// Successful response for `position_details`.
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct PositionDetailsResponse {
86    /// The requested Position.
87    #[serde(rename = "position", default, skip_serializing_if = "Option::is_none")]
88    pub position: Option<Position>,
89    /// The ID of the most recent Transaction created for the Account
90    #[serde(
91        rename = "lastTransactionID",
92        default,
93        skip_serializing_if = "Option::is_none"
94    )]
95    pub last_transaction_id: Option<TransactionID>,
96}
97
98impl Client {
99    /// Position details.
100    pub async fn position_details(
101        &self,
102        account_id: &AccountID,
103        instrument: &InstrumentName,
104    ) -> std::result::Result<
105        crate::ApiResponse<PositionDetailsResponse>,
106        crate::OperationError<crate::GenericRejection>,
107    > {
108        let account_id_encoded = crate::client::segment(account_id.as_str());
109        let instrument_encoded = crate::client::segment(instrument.as_str());
110        let path = format!("/v3/accounts/{account_id_encoded}/positions/{instrument_encoded}");
111        self.execute(Method::GET, &path, None::<&()>, None::<&()>, None, None)
112            .await
113    }
114}
115
116/// Request body for `close_position`.
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118#[serde(rename_all = "camelCase")]
119pub struct ClosePositionBody {
120    /// Indication of how much of the long Position to closeout. Either the string “ALL”, the string “NONE”, or
121    /// a DecimalNumber representing how many units of the long position to close using a PositionCloseout
122    /// MarketOrder. The units specified must always be positive.
123    #[serde(rename = "longUnits", default, skip_serializing_if = "Option::is_none")]
124    pub long_units: Option<String>,
125    /// The client extensions to add to the MarketOrder used to close the long position.
126    #[serde(
127        rename = "longClientExtensions",
128        default,
129        skip_serializing_if = "Option::is_none"
130    )]
131    pub long_client_extensions: Option<ClientExtensions>,
132    /// Indication of how much of the short Position to closeout. Either the string “ALL”, the string “NONE”, or
133    /// a DecimalNumber representing how many units of the short position to close using a PositionCloseout
134    /// MarketOrder. The units specified must always be positive.
135    #[serde(
136        rename = "shortUnits",
137        default,
138        skip_serializing_if = "Option::is_none"
139    )]
140    pub short_units: Option<String>,
141    /// The client extensions to add to the MarketOrder used to close the short position.
142    #[serde(
143        rename = "shortClientExtensions",
144        default,
145        skip_serializing_if = "Option::is_none"
146    )]
147    pub short_client_extensions: Option<ClientExtensions>,
148}
149
150/// Successful response for `close_position`.
151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct ClosePositionResponse {
154    /// The MarketOrderTransaction created to close the long Position.
155    #[serde(
156        rename = "longOrderCreateTransaction",
157        default,
158        skip_serializing_if = "Option::is_none"
159    )]
160    pub long_order_create_transaction: Option<MarketOrderTransaction>,
161    /// OrderFill Transaction that closes the long Position
162    #[serde(
163        rename = "longOrderFillTransaction",
164        default,
165        skip_serializing_if = "Option::is_none"
166    )]
167    pub long_order_fill_transaction: Option<OrderFillTransaction>,
168    /// OrderCancel Transaction that cancels the MarketOrder created to close the long Position
169    #[serde(
170        rename = "longOrderCancelTransaction",
171        default,
172        skip_serializing_if = "Option::is_none"
173    )]
174    pub long_order_cancel_transaction: Option<OrderCancelTransaction>,
175    /// The MarketOrderTransaction created to close the short Position.
176    #[serde(
177        rename = "shortOrderCreateTransaction",
178        default,
179        skip_serializing_if = "Option::is_none"
180    )]
181    pub short_order_create_transaction: Option<MarketOrderTransaction>,
182    /// OrderFill Transaction that closes the short Position
183    #[serde(
184        rename = "shortOrderFillTransaction",
185        default,
186        skip_serializing_if = "Option::is_none"
187    )]
188    pub short_order_fill_transaction: Option<OrderFillTransaction>,
189    /// OrderCancel Transaction that cancels the MarketOrder created to close the short Position
190    #[serde(
191        rename = "shortOrderCancelTransaction",
192        default,
193        skip_serializing_if = "Option::is_none"
194    )]
195    pub short_order_cancel_transaction: Option<OrderCancelTransaction>,
196    /// The IDs of all Transactions that were created while satisfying the request.
197    #[serde(
198        rename = "relatedTransactionIDs",
199        default,
200        skip_serializing_if = "Option::is_none"
201    )]
202    pub related_transaction_ids: Option<Vec<TransactionID>>,
203    /// The ID of the most recent Transaction created for the Account
204    #[serde(
205        rename = "lastTransactionID",
206        default,
207        skip_serializing_if = "Option::is_none"
208    )]
209    pub last_transaction_id: Option<TransactionID>,
210}
211
212/// Documented rejection fields for `close_position`.
213#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
214#[serde(rename_all = "camelCase")]
215pub struct ClosePositionRejection {
216    /// The Transaction created that rejects the creation of a MarketOrder to close the long Position.
217    #[serde(
218        rename = "longOrderRejectTransaction",
219        default,
220        skip_serializing_if = "Option::is_none"
221    )]
222    pub long_order_reject_transaction: Option<MarketOrderRejectTransaction>,
223    /// The Transaction created that rejects the creation of a MarketOrder to close the short Position.
224    #[serde(
225        rename = "shortOrderRejectTransaction",
226        default,
227        skip_serializing_if = "Option::is_none"
228    )]
229    pub short_order_reject_transaction: Option<MarketOrderRejectTransaction>,
230    /// The IDs of all Transactions that were created while satisfying the request.
231    #[serde(
232        rename = "relatedTransactionIDs",
233        default,
234        skip_serializing_if = "Option::is_none"
235    )]
236    pub related_transaction_ids: Option<Vec<TransactionID>>,
237    /// The ID of the most recent Transaction created for the Account
238    #[serde(
239        rename = "lastTransactionID",
240        default,
241        skip_serializing_if = "Option::is_none"
242    )]
243    pub last_transaction_id: Option<TransactionID>,
244    /// The code of the error that has occurred. This field may not be returned for some errors.
245    #[serde(rename = "errorCode", default, skip_serializing_if = "Option::is_none")]
246    pub error_code: Option<String>,
247    /// The human-readable description of the error that has occurred.
248    #[serde(
249        rename = "errorMessage",
250        default,
251        skip_serializing_if = "Option::is_none"
252    )]
253    pub error_message: Option<String>,
254}
255
256impl Client {
257    /// Close position.
258    pub async fn close_position(
259        &self,
260        account_id: &AccountID,
261        instrument: &InstrumentName,
262        body: &ClosePositionBody,
263    ) -> std::result::Result<
264        crate::ApiResponse<ClosePositionResponse>,
265        crate::OperationError<ClosePositionRejection>,
266    > {
267        let account_id_encoded = crate::client::segment(account_id.as_str());
268        let instrument_encoded = crate::client::segment(instrument.as_str());
269        let path =
270            format!("/v3/accounts/{account_id_encoded}/positions/{instrument_encoded}/close");
271        self.execute(
272            Method::PUT,
273            &path,
274            None::<&()>,
275            Some(body),
276            Some(crate::client::MutationAdmission::Exit(account_id)),
277            None,
278        )
279        .await
280    }
281}