Skip to main content

rust_okx/api/convert/
requests.rs

1use serde::Serialize;
2
3use crate::model::{
4    OrderSide, RequestValidationError, ValidateRequest, non_empty, range_u64,
5    validate_client_request_id, validate_side,
6};
7
8/// Convert mode used by currency-pair, quote, and trade requests.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
10#[non_exhaustive]
11pub enum ConvertMode {
12    /// Standard Convert flow.
13    #[serde(rename = "0")]
14    Standard,
15    /// Large-order Convert flow for eligible VIP users.
16    #[serde(rename = "1")]
17    LargeOrderVip,
18}
19
20/// Empty query for [`Convert::get_currencies`](super::Convert::get_currencies).
21#[derive(Debug, Clone, Copy, Default, Serialize)]
22pub struct ConvertCurrenciesRequest {}
23
24impl ConvertCurrenciesRequest {
25    /// Create the empty currencies request.
26    pub const fn new() -> Self {
27        Self {}
28    }
29}
30
31impl ValidateRequest for ConvertCurrenciesRequest {
32    fn validate(&self) -> Result<(), RequestValidationError> {
33        Ok(())
34    }
35}
36
37/// Query parameters for [`Convert::get_currency_pair`](super::Convert::get_currency_pair).
38#[derive(Debug, Clone, Serialize)]
39#[serde(rename_all = "camelCase")]
40pub struct ConvertCurrencyPairRequest {
41    from_ccy: String,
42    to_ccy: String,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    convert_mode: Option<ConvertMode>,
45}
46
47impl ConvertCurrencyPairRequest {
48    /// Create a currency-pair request with the required source and destination currencies.
49    pub fn new(from_ccy: impl Into<String>, to_ccy: impl Into<String>) -> Self {
50        Self {
51            from_ccy: from_ccy.into(),
52            to_ccy: to_ccy.into(),
53            convert_mode: None,
54        }
55    }
56
57    /// Select the standard or VIP large-order Convert mode.
58    pub fn convert_mode(mut self, convert_mode: ConvertMode) -> Self {
59        self.convert_mode = Some(convert_mode);
60        self
61    }
62}
63
64impl ValidateRequest for ConvertCurrencyPairRequest {
65    fn validate(&self) -> Result<(), RequestValidationError> {
66        non_empty("fromCcy", &self.from_ccy)?;
67        non_empty("toCcy", &self.to_ccy)
68    }
69}
70
71/// Request body for [`Convert::estimate_quote`](super::Convert::estimate_quote).
72#[derive(Debug, Clone, Serialize)]
73#[serde(rename_all = "camelCase")]
74pub struct ConvertQuoteRequest {
75    base_ccy: String,
76    quote_ccy: String,
77    side: OrderSide,
78    rfq_sz: String,
79    rfq_sz_ccy: String,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    cl_q_req_id: Option<String>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    tag: Option<String>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    convert_mode: Option<ConvertMode>,
86}
87
88impl ConvertQuoteRequest {
89    /// Create an estimate-quote request with all fields required by OKX.
90    pub fn new(
91        base_ccy: impl Into<String>,
92        quote_ccy: impl Into<String>,
93        side: OrderSide,
94        rfq_sz: impl Into<String>,
95        rfq_sz_ccy: impl Into<String>,
96    ) -> Self {
97        Self {
98            base_ccy: base_ccy.into(),
99            quote_ccy: quote_ccy.into(),
100            side,
101            rfq_sz: rfq_sz.into(),
102            rfq_sz_ccy: rfq_sz_ccy.into(),
103            cl_q_req_id: None,
104            tag: None,
105            convert_mode: None,
106        }
107    }
108
109    /// Set the client quote request ID (`clQReqId`).
110    pub fn client_quote_request_id(mut self, id: impl Into<String>) -> Self {
111        self.cl_q_req_id = Some(id.into());
112        self
113    }
114
115    /// Set the broker order tag.
116    pub fn tag(mut self, tag: impl Into<String>) -> Self {
117        self.tag = Some(tag.into());
118        self
119    }
120
121    /// Select the standard or VIP large-order Convert mode.
122    pub fn convert_mode(mut self, convert_mode: ConvertMode) -> Self {
123        self.convert_mode = Some(convert_mode);
124        self
125    }
126}
127
128impl ValidateRequest for ConvertQuoteRequest {
129    fn validate(&self) -> Result<(), RequestValidationError> {
130        non_empty("baseCcy", &self.base_ccy)?;
131        non_empty("quoteCcy", &self.quote_ccy)?;
132        validate_side(&self.side)?;
133        non_empty("rfqSz", &self.rfq_sz)?;
134        non_empty("rfqSzCcy", &self.rfq_sz_ccy)?;
135        validate_client_request_id("clQReqId", self.cl_q_req_id.as_deref())
136    }
137}
138
139/// Request body for [`Convert::convert_trade`](super::Convert::convert_trade).
140#[derive(Debug, Clone, Serialize)]
141#[serde(rename_all = "camelCase")]
142pub struct ConvertTradeRequest {
143    quote_id: String,
144    base_ccy: String,
145    quote_ccy: String,
146    side: OrderSide,
147    sz: String,
148    sz_ccy: String,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    cl_t_req_id: Option<String>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    tag: Option<String>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    convert_mode: Option<ConvertMode>,
155}
156
157impl ConvertTradeRequest {
158    /// Create a Convert trade request with all fields required by OKX.
159    pub fn new(
160        quote_id: impl Into<String>,
161        base_ccy: impl Into<String>,
162        quote_ccy: impl Into<String>,
163        side: OrderSide,
164        sz: impl Into<String>,
165        sz_ccy: impl Into<String>,
166    ) -> Self {
167        Self {
168            quote_id: quote_id.into(),
169            base_ccy: base_ccy.into(),
170            quote_ccy: quote_ccy.into(),
171            side,
172            sz: sz.into(),
173            sz_ccy: sz_ccy.into(),
174            cl_t_req_id: None,
175            tag: None,
176            convert_mode: None,
177        }
178    }
179
180    /// Set the client trade request ID (`clTReqId`).
181    pub fn client_trade_request_id(mut self, id: impl Into<String>) -> Self {
182        self.cl_t_req_id = Some(id.into());
183        self
184    }
185
186    /// Set the broker order tag.
187    pub fn tag(mut self, tag: impl Into<String>) -> Self {
188        self.tag = Some(tag.into());
189        self
190    }
191
192    /// Select the standard or VIP large-order Convert mode.
193    pub fn convert_mode(mut self, convert_mode: ConvertMode) -> Self {
194        self.convert_mode = Some(convert_mode);
195        self
196    }
197}
198
199impl ValidateRequest for ConvertTradeRequest {
200    fn validate(&self) -> Result<(), RequestValidationError> {
201        non_empty("quoteId", &self.quote_id)?;
202        non_empty("baseCcy", &self.base_ccy)?;
203        non_empty("quoteCcy", &self.quote_ccy)?;
204        validate_side(&self.side)?;
205        non_empty("sz", &self.sz)?;
206        non_empty("szCcy", &self.sz_ccy)?;
207        validate_client_request_id("clTReqId", self.cl_t_req_id.as_deref())
208    }
209}
210
211/// Query parameters for [`Convert::get_convert_history`](super::Convert::get_convert_history).
212#[derive(Debug, Clone, Default, Serialize)]
213#[serde(rename_all = "camelCase")]
214pub struct ConvertHistoryRequest {
215    #[serde(skip_serializing_if = "Option::is_none")]
216    cl_t_req_id: Option<String>,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    after: Option<String>,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    before: Option<String>,
221    #[serde(skip_serializing_if = "Option::is_none")]
222    limit: Option<String>,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    tag: Option<String>,
225}
226
227impl ConvertHistoryRequest {
228    /// Create an empty history query.
229    pub fn new() -> Self {
230        Self::default()
231    }
232
233    /// Filter by the client trade request ID (`clTReqId`).
234    pub fn client_trade_request_id(mut self, id: impl Into<String>) -> Self {
235        self.cl_t_req_id = Some(id.into());
236        self
237    }
238
239    /// Return records earlier than this millisecond timestamp.
240    pub fn after(mut self, timestamp_ms: u64) -> Self {
241        self.after = Some(timestamp_ms.to_string());
242        self
243    }
244
245    /// Return records newer than this millisecond timestamp.
246    pub fn before(mut self, timestamp_ms: u64) -> Self {
247        self.before = Some(timestamp_ms.to_string());
248        self
249    }
250
251    /// Set the maximum number of returned rows. OKX accepts values from 1 to 100.
252    pub fn limit(mut self, limit: u8) -> Self {
253        self.limit = Some(limit.to_string());
254        self
255    }
256
257    /// Filter by the broker order tag used by the original Convert trade.
258    pub fn tag(mut self, tag: impl Into<String>) -> Self {
259        self.tag = Some(tag.into());
260        self
261    }
262}
263
264impl ValidateRequest for ConvertHistoryRequest {
265    fn validate(&self) -> Result<(), RequestValidationError> {
266        validate_client_request_id("clTReqId", self.cl_t_req_id.as_deref())?;
267
268        match &self.limit {
269            Some(limit) => {
270                let parsed = limit
271                    .parse::<u64>()
272                    .expect("ConvertHistoryRequest::limit stores a u8 as decimal text");
273
274                range_u64("limit", parsed, 1, 100)
275            }
276            None => Ok(()),
277        }
278    }
279}