Skip to main content

rust_okx/api/public_data/requests/
edge.rs

1use serde::Serialize;
2
3use crate::model::{
4    RequestValidationError, ValidateRequest, at_least_one, non_empty, one_of, optional_non_empty,
5    optional_one_of, optional_unsigned_integer_string,
6};
7
8/// Query parameters for `GET /api/v5/public/opt-summary`.
9#[derive(Debug, Clone, Serialize)]
10pub struct OptionSummaryRequest {
11    #[serde(rename = "instFamily")]
12    inst_family: String,
13    #[serde(rename = "expTime", skip_serializing_if = "Option::is_none")]
14    exp_time: Option<String>,
15}
16
17impl OptionSummaryRequest {
18    /// Create a query for an option instrument family, such as `BTC-USD`.
19    pub fn new(inst_family: impl Into<String>) -> Self {
20        Self {
21            inst_family: inst_family.into(),
22            exp_time: None,
23        }
24    }
25
26    /// Restrict the result to one expiration timestamp in milliseconds.
27    pub fn expiration(mut self, value: impl Into<String>) -> Self {
28        self.exp_time = Some(value.into());
29        self
30    }
31}
32
33impl ValidateRequest for OptionSummaryRequest {
34    fn validate(&self) -> Result<(), RequestValidationError> {
35        non_empty("instFamily", &self.inst_family)?;
36        optional_unsigned_integer_string("expTime", self.exp_time.as_deref())
37    }
38}
39
40/// Empty query for `GET /api/v5/public/interest-rate-loan-quota`.
41#[derive(Debug, Clone, Default, Serialize)]
42pub struct InterestRateLoanQuotaRequest {}
43
44impl InterestRateLoanQuotaRequest {
45    /// Create the parameterless query.
46    pub fn new() -> Self {
47        Self::default()
48    }
49}
50
51impl ValidateRequest for InterestRateLoanQuotaRequest {
52    fn validate(&self) -> Result<(), RequestValidationError> {
53        Ok(())
54    }
55}
56
57/// Query parameters for `GET /api/v5/public/instrument-tick-bands`.
58#[derive(Debug, Clone, Serialize)]
59pub struct InstrumentTickBandsRequest {
60    #[serde(rename = "instType")]
61    inst_type: String,
62}
63
64impl InstrumentTickBandsRequest {
65    /// Create a tick-band query for `FUTURES` or `OPTION` instruments.
66    pub fn new(inst_type: impl Into<String>) -> Self {
67        Self {
68            inst_type: inst_type.into(),
69        }
70    }
71}
72
73impl ValidateRequest for InstrumentTickBandsRequest {
74    fn validate(&self) -> Result<(), RequestValidationError> {
75        one_of(
76            "instType",
77            &self.inst_type,
78            &["FUTURES", "OPTION"],
79            "FUTURES or OPTION",
80        )
81    }
82}
83
84/// Query parameters for `GET /api/v5/public/underlying`.
85#[derive(Debug, Clone, Serialize)]
86pub struct UnderlyingRequest {
87    #[serde(rename = "instType")]
88    inst_type: String,
89}
90
91impl UnderlyingRequest {
92    /// Create a query for `SWAP`, `FUTURES`, or `OPTION` instruments.
93    pub fn new(inst_type: impl Into<String>) -> Self {
94        Self {
95            inst_type: inst_type.into(),
96        }
97    }
98}
99
100impl ValidateRequest for UnderlyingRequest {
101    fn validate(&self) -> Result<(), RequestValidationError> {
102        one_of(
103            "instType",
104            &self.inst_type,
105            &["SWAP", "FUTURES", "OPTION"],
106            "SWAP, FUTURES, or OPTION",
107        )
108    }
109}
110
111/// Query parameters for `GET /api/v5/public/option-trades`.
112#[derive(Debug, Clone, Default, Serialize)]
113pub struct PublicOptionTradesRequest {
114    #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
115    inst_id: Option<String>,
116    #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
117    inst_family: Option<String>,
118    #[serde(rename = "optType", skip_serializing_if = "Option::is_none")]
119    option_type: Option<String>,
120}
121
122impl PublicOptionTradesRequest {
123    /// Create an unfiltered option-trades query.
124    pub fn new() -> Self {
125        Self::default()
126    }
127
128    /// Restrict results to one option instrument.
129    pub fn inst_id(mut self, value: impl Into<String>) -> Self {
130        self.inst_id = Some(value.into());
131        self
132    }
133
134    /// Restrict results to one option instrument family.
135    pub fn inst_family(mut self, value: impl Into<String>) -> Self {
136        self.inst_family = Some(value.into());
137        self
138    }
139
140    /// Restrict results to calls (`C`) or puts (`P`).
141    pub fn option_type(mut self, value: impl Into<String>) -> Self {
142        self.option_type = Some(value.into());
143        self
144    }
145}
146
147impl ValidateRequest for PublicOptionTradesRequest {
148    fn validate(&self) -> Result<(), RequestValidationError> {
149        optional_non_empty("instId", self.inst_id.as_deref())?;
150        optional_non_empty("instFamily", self.inst_family.as_deref())?;
151        optional_one_of(
152            "optType",
153            self.option_type.as_deref(),
154            &["C", "P"],
155            "C or P",
156        )?;
157        at_least_one(
158            "instId, instFamily",
159            &[self.inst_id.is_some(), self.inst_family.is_some()],
160        )
161    }
162}
163
164/// Query parameters for `GET /api/v5/public/market-data-history`.
165#[derive(Debug, Clone, Default, Serialize)]
166pub struct MarketDataHistoryRequest {
167    #[serde(skip_serializing_if = "Option::is_none")]
168    module: Option<String>,
169    #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
170    inst_type: Option<String>,
171    #[serde(rename = "instIdList", skip_serializing_if = "Option::is_none")]
172    inst_id_list: Option<String>,
173    #[serde(rename = "dateAggrType", skip_serializing_if = "Option::is_none")]
174    date_aggr_type: Option<String>,
175    #[serde(skip_serializing_if = "Option::is_none")]
176    begin: Option<String>,
177    #[serde(skip_serializing_if = "Option::is_none")]
178    end: Option<String>,
179}
180
181impl MarketDataHistoryRequest {
182    /// Create an empty market-data-history query.
183    pub fn new() -> Self {
184        Self::default()
185    }
186
187    /// Set the OKX market-data module identifier.
188    pub fn module(mut self, value: impl Into<String>) -> Self {
189        self.module = Some(value.into());
190        self
191    }
192
193    /// Set the instrument type filter.
194    pub fn inst_type(mut self, value: impl Into<String>) -> Self {
195        self.inst_type = Some(value.into());
196        self
197    }
198
199    /// Set a comma-separated instrument-ID list.
200    pub fn inst_id_list(mut self, value: impl Into<String>) -> Self {
201        self.inst_id_list = Some(value.into());
202        self
203    }
204
205    /// Set the documented date aggregation type.
206    pub fn date_aggregation(mut self, value: impl Into<String>) -> Self {
207        self.date_aggr_type = Some(value.into());
208        self
209    }
210
211    /// Set the inclusive begin timestamp in milliseconds.
212    pub fn begin(mut self, value: impl Into<String>) -> Self {
213        self.begin = Some(value.into());
214        self
215    }
216
217    /// Set the inclusive end timestamp in milliseconds.
218    pub fn end(mut self, value: impl Into<String>) -> Self {
219        self.end = Some(value.into());
220        self
221    }
222}
223
224impl ValidateRequest for MarketDataHistoryRequest {
225    fn validate(&self) -> Result<(), RequestValidationError> {
226        optional_non_empty("module", self.module.as_deref())?;
227        optional_one_of(
228            "instType",
229            self.inst_type.as_deref(),
230            &["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"],
231            "SPOT, MARGIN, SWAP, FUTURES, or OPTION",
232        )?;
233        optional_non_empty("instIdList", self.inst_id_list.as_deref())?;
234        optional_non_empty("dateAggrType", self.date_aggr_type.as_deref())?;
235        optional_unsigned_integer_string("begin", self.begin.as_deref())?;
236        optional_unsigned_integer_string("end", self.end.as_deref())?;
237        Ok(())
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn market_history_uses_okx_field_names() {
247        let request = MarketDataHistoryRequest::new()
248            .module("2")
249            .inst_type("SPOT")
250            .inst_id_list("BTC-USDT")
251            .date_aggregation("daily");
252        request.validate().unwrap();
253        let value = serde_json::to_value(request).unwrap();
254        assert_eq!(value["instType"], "SPOT");
255        assert_eq!(value["instIdList"], "BTC-USDT");
256        assert_eq!(value["dateAggrType"], "daily");
257    }
258
259    #[test]
260    fn option_trades_requires_an_instrument_scope() {
261        assert!(PublicOptionTradesRequest::new().validate().is_err());
262    }
263}