Skip to main content

oanda_client/models/
trade.rs

1//! OANDA v20 trade definitions.
2// Shared imports vary by definition family; unused ones are deliberately allowed.
3#[allow(unused_imports)]
4use super::*;
5#[allow(unused_imports)]
6use crate::ids::*;
7#[allow(unused_imports)]
8use crate::timestamp::Timestamp;
9#[allow(unused_imports)]
10use rust_decimal::Decimal;
11#[allow(unused_imports)]
12use serde::{Deserialize, Serialize};
13
14/// The current state of the Trade.
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16#[non_exhaustive]
17pub enum TradeState {
18    /// The Trade is currently open
19    Open,
20    /// The Trade has been fully closed
21    Closed,
22    /// The Trade will be closed as soon as the trade’s instrument becomes tradeable
23    CloseWhenTradeable,
24    /// An unrecognized provider value, preserved for forward compatibility.
25    Unknown(String),
26}
27
28impl TradeState {
29    /// Return the exact provider spelling.
30    pub fn as_str(&self) -> &str {
31        match self {
32            Self::Open => "OPEN",
33            Self::Closed => "CLOSED",
34            Self::CloseWhenTradeable => "CLOSE_WHEN_TRADEABLE",
35            Self::Unknown(value) => value,
36        }
37    }
38}
39
40impl Serialize for TradeState {
41    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
42        serializer.serialize_str(self.as_str())
43    }
44}
45
46impl<'de> Deserialize<'de> for TradeState {
47    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
48        let value = String::deserialize(deserializer)?;
49        Ok(match value.as_str() {
50            "OPEN" => Self::Open,
51            "CLOSED" => Self::Closed,
52            "CLOSE_WHEN_TRADEABLE" => Self::CloseWhenTradeable,
53            _ => Self::Unknown(value),
54        })
55    }
56}
57
58/// The state to filter the Trades by
59#[derive(Debug, Clone, PartialEq, Eq, Hash)]
60#[non_exhaustive]
61pub enum TradeStateFilter {
62    /// The Trades that are currently open
63    Open,
64    /// The Trades that have been fully closed
65    Closed,
66    /// The Trades that will be closed as soon as the trades’ instrument becomes tradeable
67    CloseWhenTradeable,
68    /// The Trades that are in any of the possible states listed above.
69    All,
70    /// An unrecognized provider value, preserved for forward compatibility.
71    Unknown(String),
72}
73
74impl TradeStateFilter {
75    /// Return the exact provider spelling.
76    pub fn as_str(&self) -> &str {
77        match self {
78            Self::Open => "OPEN",
79            Self::Closed => "CLOSED",
80            Self::CloseWhenTradeable => "CLOSE_WHEN_TRADEABLE",
81            Self::All => "ALL",
82            Self::Unknown(value) => value,
83        }
84    }
85}
86
87impl Serialize for TradeStateFilter {
88    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
89        serializer.serialize_str(self.as_str())
90    }
91}
92
93impl<'de> Deserialize<'de> for TradeStateFilter {
94    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
95        let value = String::deserialize(deserializer)?;
96        Ok(match value.as_str() {
97            "OPEN" => Self::Open,
98            "CLOSED" => Self::Closed,
99            "CLOSE_WHEN_TRADEABLE" => Self::CloseWhenTradeable,
100            "ALL" => Self::All,
101            _ => Self::Unknown(value),
102        })
103    }
104}
105
106/// The specification of a Trade within an Account. This includes the full representation of the Trade’s
107/// dependent Orders in addition to the IDs of those Orders.
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct Trade {
111    /// The Trade’s identifier, unique within the Trade’s Account.
112    #[serde(rename = "id", default, skip_serializing_if = "Option::is_none")]
113    pub id: Option<TradeID>,
114    /// The Trade’s Instrument.
115    #[serde(
116        rename = "instrument",
117        default,
118        skip_serializing_if = "Option::is_none"
119    )]
120    pub instrument: Option<InstrumentName>,
121    /// The execution price of the Trade.
122    #[serde(rename = "price", default, skip_serializing_if = "Option::is_none")]
123    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
124    pub price: Option<Decimal>,
125    /// The date/time when the Trade was opened.
126    #[serde(rename = "openTime", default, skip_serializing_if = "Option::is_none")]
127    pub open_time: Option<Timestamp>,
128    /// The current state of the Trade.
129    #[serde(rename = "state", default, skip_serializing_if = "Option::is_none")]
130    pub state: Option<TradeState>,
131    /// The initial size of the Trade. Negative values indicate a short Trade, and positive values indicate a
132    /// long Trade.
133    #[serde(
134        rename = "initialUnits",
135        default,
136        skip_serializing_if = "Option::is_none"
137    )]
138    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
139    pub initial_units: Option<Decimal>,
140    /// The margin required at the time the Trade was created. Note, this is the ‘pure’ margin required, it is
141    /// not the ‘effective’ margin used that factors in the trade risk if a GSLO is attached to the trade.
142    #[serde(
143        rename = "initialMarginRequired",
144        default,
145        skip_serializing_if = "Option::is_none"
146    )]
147    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
148    pub initial_margin_required: Option<Decimal>,
149    /// The number of units currently open for the Trade. This value is reduced to 0.0 as the Trade is closed.
150    #[serde(
151        rename = "currentUnits",
152        default,
153        skip_serializing_if = "Option::is_none"
154    )]
155    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
156    pub current_units: Option<Decimal>,
157    /// The total profit/loss realized on the closed portion of the Trade.
158    #[serde(
159        rename = "realizedPL",
160        default,
161        skip_serializing_if = "Option::is_none"
162    )]
163    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
164    pub realized_pl: Option<Decimal>,
165    /// The unrealized profit/loss on the open portion of the Trade.
166    #[serde(
167        rename = "unrealizedPL",
168        default,
169        skip_serializing_if = "Option::is_none"
170    )]
171    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
172    pub unrealized_pl: Option<Decimal>,
173    /// Margin currently used by the Trade.
174    #[serde(
175        rename = "marginUsed",
176        default,
177        skip_serializing_if = "Option::is_none"
178    )]
179    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
180    pub margin_used: Option<Decimal>,
181    /// The average closing price of the Trade. Only present if the Trade has been closed or reduced at least
182    /// once.
183    #[serde(
184        rename = "averageClosePrice",
185        default,
186        skip_serializing_if = "Option::is_none"
187    )]
188    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
189    pub average_close_price: Option<Decimal>,
190    /// The IDs of the Transactions that have closed portions of this Trade.
191    #[serde(
192        rename = "closingTransactionIDs",
193        default,
194        skip_serializing_if = "Option::is_none"
195    )]
196    pub closing_transaction_ids: Option<Vec<TransactionID>>,
197    /// The financing paid/collected for this Trade.
198    #[serde(rename = "financing", default, skip_serializing_if = "Option::is_none")]
199    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
200    pub financing: Option<Decimal>,
201    /// The dividend adjustment paid for this Trade.
202    #[serde(
203        rename = "dividendAdjustment",
204        default,
205        skip_serializing_if = "Option::is_none"
206    )]
207    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
208    pub dividend_adjustment: Option<Decimal>,
209    /// The date/time when the Trade was fully closed. Only provided for Trades whose state is CLOSED.
210    #[serde(rename = "closeTime", default, skip_serializing_if = "Option::is_none")]
211    pub close_time: Option<Timestamp>,
212    /// The client extensions of the Trade.
213    #[serde(
214        rename = "clientExtensions",
215        default,
216        skip_serializing_if = "Option::is_none"
217    )]
218    pub client_extensions: Option<ClientExtensions>,
219    /// Full representation of the Trade’s Take Profit Order, only provided if such an Order exists.
220    #[serde(
221        rename = "takeProfitOrder",
222        default,
223        skip_serializing_if = "Option::is_none"
224    )]
225    pub take_profit_order: Option<TakeProfitOrder>,
226    /// Full representation of the Trade’s Stop Loss Order, only provided if such an Order exists.
227    #[serde(
228        rename = "stopLossOrder",
229        default,
230        skip_serializing_if = "Option::is_none"
231    )]
232    pub stop_loss_order: Option<StopLossOrder>,
233    /// Full representation of the Trade’s Trailing Stop Loss Order, only provided if such an Order exists.
234    #[serde(
235        rename = "trailingStopLossOrder",
236        default,
237        skip_serializing_if = "Option::is_none"
238    )]
239    pub trailing_stop_loss_order: Option<TrailingStopLossOrder>,
240}
241
242/// The summary of a Trade within an Account. This representation does not provide the full details of the
243/// Trade’s dependent Orders.
244#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
245#[serde(rename_all = "camelCase")]
246pub struct TradeSummary {
247    /// The Trade’s identifier, unique within the Trade’s Account.
248    #[serde(rename = "id", default, skip_serializing_if = "Option::is_none")]
249    pub id: Option<TradeID>,
250    /// The Trade’s Instrument.
251    #[serde(
252        rename = "instrument",
253        default,
254        skip_serializing_if = "Option::is_none"
255    )]
256    pub instrument: Option<InstrumentName>,
257    /// The execution price of the Trade.
258    #[serde(rename = "price", default, skip_serializing_if = "Option::is_none")]
259    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
260    pub price: Option<Decimal>,
261    /// The date/time when the Trade was opened.
262    #[serde(rename = "openTime", default, skip_serializing_if = "Option::is_none")]
263    pub open_time: Option<Timestamp>,
264    /// The current state of the Trade.
265    #[serde(rename = "state", default, skip_serializing_if = "Option::is_none")]
266    pub state: Option<TradeState>,
267    /// The initial size of the Trade. Negative values indicate a short Trade, and positive values indicate a
268    /// long Trade.
269    #[serde(
270        rename = "initialUnits",
271        default,
272        skip_serializing_if = "Option::is_none"
273    )]
274    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
275    pub initial_units: Option<Decimal>,
276    /// The margin required at the time the Trade was created. Note, this is the ‘pure’ margin required, it is
277    /// not the ‘effective’ margin used that factors in the trade risk if a GSLO is attached to the trade.
278    #[serde(
279        rename = "initialMarginRequired",
280        default,
281        skip_serializing_if = "Option::is_none"
282    )]
283    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
284    pub initial_margin_required: Option<Decimal>,
285    /// The number of units currently open for the Trade. This value is reduced to 0.0 as the Trade is closed.
286    #[serde(
287        rename = "currentUnits",
288        default,
289        skip_serializing_if = "Option::is_none"
290    )]
291    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
292    pub current_units: Option<Decimal>,
293    /// The total profit/loss realized on the closed portion of the Trade.
294    #[serde(
295        rename = "realizedPL",
296        default,
297        skip_serializing_if = "Option::is_none"
298    )]
299    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
300    pub realized_pl: Option<Decimal>,
301    /// The unrealized profit/loss on the open portion of the Trade.
302    #[serde(
303        rename = "unrealizedPL",
304        default,
305        skip_serializing_if = "Option::is_none"
306    )]
307    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
308    pub unrealized_pl: Option<Decimal>,
309    /// Margin currently used by the Trade.
310    #[serde(
311        rename = "marginUsed",
312        default,
313        skip_serializing_if = "Option::is_none"
314    )]
315    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
316    pub margin_used: Option<Decimal>,
317    /// The average closing price of the Trade. Only present if the Trade has been closed or reduced at least
318    /// once.
319    #[serde(
320        rename = "averageClosePrice",
321        default,
322        skip_serializing_if = "Option::is_none"
323    )]
324    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
325    pub average_close_price: Option<Decimal>,
326    /// The IDs of the Transactions that have closed portions of this Trade.
327    #[serde(
328        rename = "closingTransactionIDs",
329        default,
330        skip_serializing_if = "Option::is_none"
331    )]
332    pub closing_transaction_ids: Option<Vec<TransactionID>>,
333    /// The financing paid/collected for this Trade.
334    #[serde(rename = "financing", default, skip_serializing_if = "Option::is_none")]
335    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
336    pub financing: Option<Decimal>,
337    /// The dividend adjustment paid for this Trade.
338    #[serde(
339        rename = "dividendAdjustment",
340        default,
341        skip_serializing_if = "Option::is_none"
342    )]
343    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
344    pub dividend_adjustment: Option<Decimal>,
345    /// The date/time when the Trade was fully closed. Only provided for Trades whose state is CLOSED.
346    #[serde(rename = "closeTime", default, skip_serializing_if = "Option::is_none")]
347    pub close_time: Option<Timestamp>,
348    /// The client extensions of the Trade.
349    #[serde(
350        rename = "clientExtensions",
351        default,
352        skip_serializing_if = "Option::is_none"
353    )]
354    pub client_extensions: Option<ClientExtensions>,
355    /// ID of the Trade’s Take Profit Order, only provided if such an Order exists.
356    #[serde(
357        rename = "takeProfitOrderID",
358        default,
359        skip_serializing_if = "Option::is_none"
360    )]
361    pub take_profit_order_id: Option<OrderID>,
362    /// ID of the Trade’s Stop Loss Order, only provided if such an Order exists.
363    #[serde(
364        rename = "stopLossOrderID",
365        default,
366        skip_serializing_if = "Option::is_none"
367    )]
368    pub stop_loss_order_id: Option<OrderID>,
369    /// ID of the Trade’s Guaranteed Stop Loss Order, only provided if such an Order exists.
370    #[serde(
371        rename = "guaranteedStopLossOrderID",
372        default,
373        skip_serializing_if = "Option::is_none"
374    )]
375    pub guaranteed_stop_loss_order_id: Option<OrderID>,
376    /// ID of the Trade’s Trailing Stop Loss Order, only provided if such an Order exists.
377    #[serde(
378        rename = "trailingStopLossOrderID",
379        default,
380        skip_serializing_if = "Option::is_none"
381    )]
382    pub trailing_stop_loss_order_id: Option<OrderID>,
383}
384
385/// The dynamic (calculated) state of an open Trade
386#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
387#[serde(rename_all = "camelCase")]
388pub struct CalculatedTradeState {
389    /// The Trade’s ID.
390    #[serde(rename = "id", default, skip_serializing_if = "Option::is_none")]
391    pub id: Option<TradeID>,
392    /// The Trade’s unrealized profit/loss.
393    #[serde(
394        rename = "unrealizedPL",
395        default,
396        skip_serializing_if = "Option::is_none"
397    )]
398    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
399    pub unrealized_pl: Option<Decimal>,
400    /// Margin currently used by the Trade.
401    #[serde(
402        rename = "marginUsed",
403        default,
404        skip_serializing_if = "Option::is_none"
405    )]
406    #[serde(deserialize_with = "crate::decimal_wire::optional_number_or_string")]
407    pub margin_used: Option<Decimal>,
408}
409
410/// The classification of TradePLs.
411#[derive(Debug, Clone, PartialEq, Eq, Hash)]
412#[non_exhaustive]
413pub enum TradePL {
414    /// An open Trade currently has a positive (profitable) unrealized P/L, or a closed Trade realized a
415    /// positive amount of P/L.
416    Positive,
417    /// An open Trade currently has a negative (losing) unrealized P/L, or a closed Trade realized a negative
418    /// amount of P/L.
419    Negative,
420    /// An open Trade currently has unrealized P/L of zero (neither profitable nor losing), or a closed Trade
421    /// realized a P/L amount of zero.
422    Zero,
423    /// An unrecognized provider value, preserved for forward compatibility.
424    Unknown(String),
425}
426
427impl TradePL {
428    /// Return the exact provider spelling.
429    pub fn as_str(&self) -> &str {
430        match self {
431            Self::Positive => "POSITIVE",
432            Self::Negative => "NEGATIVE",
433            Self::Zero => "ZERO",
434            Self::Unknown(value) => value,
435        }
436    }
437}
438
439impl Serialize for TradePL {
440    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
441        serializer.serialize_str(self.as_str())
442    }
443}
444
445impl<'de> Deserialize<'de> for TradePL {
446    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
447        let value = String::deserialize(deserializer)?;
448        Ok(match value.as_str() {
449            "POSITIVE" => Self::Positive,
450            "NEGATIVE" => Self::Negative,
451            "ZERO" => Self::Zero,
452            _ => Self::Unknown(value),
453        })
454    }
455}