hktrading_client/
lib.rs

1#[allow(unused_imports)]
2use progenitor_client::{encode_path, RequestBuilderExt};
3#[allow(unused_imports)]
4pub use progenitor_client::{ByteStream, Error, ResponseValue};
5#[allow(unused_imports)]
6use reqwest::header::{HeaderMap, HeaderValue};
7/// Types used as operation parameters and responses.
8#[allow(clippy::all)]
9pub mod types {
10    use serde::{Deserialize, Serialize};
11    #[allow(unused_imports)]
12    use std::convert::TryFrom;
13    /// Error types.
14    pub mod error {
15        /// Error from a TryFrom or FromStr implementation.
16        pub struct ConversionError(std::borrow::Cow<'static, str>);
17        impl std::error::Error for ConversionError {}
18        impl std::fmt::Display for ConversionError {
19            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
20                std::fmt::Display::fmt(&self.0, f)
21            }
22        }
23
24        impl std::fmt::Debug for ConversionError {
25            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
26                std::fmt::Debug::fmt(&self.0, f)
27            }
28        }
29
30        impl From<&'static str> for ConversionError {
31            fn from(value: &'static str) -> Self {
32                Self(value.into())
33            }
34        }
35
36        impl From<String> for ConversionError {
37            fn from(value: String) -> Self {
38                Self(value.into())
39            }
40        }
41    }
42
43    ///Candles
44    ///
45    /// <details><summary>JSON schema</summary>
46    ///
47    /// ```json
48    ///{
49    ///  "type": "object",
50    ///  "properties": {
51    ///    "closes": {
52    ///      "type": "array",
53    ///      "items": {
54    ///        "type": "number",
55    ///        "format": "double"
56    ///      }
57    ///    },
58    ///    "highs": {
59    ///      "type": "array",
60    ///      "items": {
61    ///        "type": "number",
62    ///        "format": "double"
63    ///      }
64    ///    },
65    ///    "lows": {
66    ///      "type": "array",
67    ///      "items": {
68    ///        "type": "number",
69    ///        "format": "double"
70    ///      }
71    ///    },
72    ///    "opens": {
73    ///      "type": "array",
74    ///      "items": {
75    ///        "type": "number",
76    ///        "format": "double"
77    ///      }
78    ///    },
79    ///    "times": {
80    ///      "type": "array",
81    ///      "items": {
82    ///        "type": "integer",
83    ///        "format": "int64"
84    ///      }
85    ///    },
86    ///    "vols": {
87    ///      "type": "array",
88    ///      "items": {
89    ///        "type": "number",
90    ///        "format": "double"
91    ///      }
92    ///    }
93    ///  }
94    ///}
95    /// ```
96    /// </details>
97    #[derive(Clone, Debug, Deserialize, Serialize)]
98    pub struct Candles {
99        #[serde(default, skip_serializing_if = "Vec::is_empty")]
100        pub closes: Vec<f64>,
101        #[serde(default, skip_serializing_if = "Vec::is_empty")]
102        pub highs: Vec<f64>,
103        #[serde(default, skip_serializing_if = "Vec::is_empty")]
104        pub lows: Vec<f64>,
105        #[serde(default, skip_serializing_if = "Vec::is_empty")]
106        pub opens: Vec<f64>,
107        #[serde(default, skip_serializing_if = "Vec::is_empty")]
108        pub times: Vec<i64>,
109        #[serde(default, skip_serializing_if = "Vec::is_empty")]
110        pub vols: Vec<f64>,
111    }
112
113    impl From<&Candles> for Candles {
114        fn from(value: &Candles) -> Self {
115            value.clone()
116        }
117    }
118
119    ///HkError
120    ///
121    /// <details><summary>JSON schema</summary>
122    ///
123    /// ```json
124    ///{
125    ///  "type": "object",
126    ///  "properties": {
127    ///    "code": {
128    ///      "type": "integer",
129    ///      "format": "int32"
130    ///    },
131    ///    "message": {
132    ///      "type": "string"
133    ///    }
134    ///  }
135    ///}
136    /// ```
137    /// </details>
138    #[derive(Clone, Debug, Deserialize, Serialize)]
139    pub struct HkError {
140        #[serde(default, skip_serializing_if = "Option::is_none")]
141        pub code: Option<i32>,
142        #[serde(default, skip_serializing_if = "Option::is_none")]
143        pub message: Option<String>,
144    }
145
146    impl From<&HkError> for HkError {
147        fn from(value: &HkError) -> Self {
148            value.clone()
149        }
150    }
151
152    ///Resolution
153    ///
154    /// <details><summary>JSON schema</summary>
155    ///
156    /// ```json
157    ///{
158    ///  "type": "string",
159    ///  "enum": [
160    ///    "m1",
161    ///    "m5",
162    ///    "m15",
163    ///    "m30",
164    ///    "h1",
165    ///    "h4",
166    ///    "d1",
167    ///    "w1"
168    ///  ]
169    ///}
170    /// ```
171    /// </details>
172    #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
173    pub enum Resolution {
174        #[serde(rename = "m1")]
175        M1,
176        #[serde(rename = "m5")]
177        M5,
178        #[serde(rename = "m15")]
179        M15,
180        #[serde(rename = "m30")]
181        M30,
182        #[serde(rename = "h1")]
183        H1,
184        #[serde(rename = "h4")]
185        H4,
186        #[serde(rename = "d1")]
187        D1,
188        #[serde(rename = "w1")]
189        W1,
190    }
191
192    impl From<&Resolution> for Resolution {
193        fn from(value: &Resolution) -> Self {
194            value.clone()
195        }
196    }
197
198    impl ToString for Resolution {
199        fn to_string(&self) -> String {
200            match *self {
201                Self::M1 => "m1".to_string(),
202                Self::M5 => "m5".to_string(),
203                Self::M15 => "m15".to_string(),
204                Self::M30 => "m30".to_string(),
205                Self::H1 => "h1".to_string(),
206                Self::H4 => "h4".to_string(),
207                Self::D1 => "d1".to_string(),
208                Self::W1 => "w1".to_string(),
209            }
210        }
211    }
212
213    impl std::str::FromStr for Resolution {
214        type Err = self::error::ConversionError;
215        fn from_str(value: &str) -> Result<Self, self::error::ConversionError> {
216            match value {
217                "m1" => Ok(Self::M1),
218                "m5" => Ok(Self::M5),
219                "m15" => Ok(Self::M15),
220                "m30" => Ok(Self::M30),
221                "h1" => Ok(Self::H1),
222                "h4" => Ok(Self::H4),
223                "d1" => Ok(Self::D1),
224                "w1" => Ok(Self::W1),
225                _ => Err("invalid value".into()),
226            }
227        }
228    }
229
230    impl std::convert::TryFrom<&str> for Resolution {
231        type Error = self::error::ConversionError;
232        fn try_from(value: &str) -> Result<Self, self::error::ConversionError> {
233            value.parse()
234        }
235    }
236
237    impl std::convert::TryFrom<&String> for Resolution {
238        type Error = self::error::ConversionError;
239        fn try_from(value: &String) -> Result<Self, self::error::ConversionError> {
240            value.parse()
241        }
242    }
243
244    impl std::convert::TryFrom<String> for Resolution {
245        type Error = self::error::ConversionError;
246        fn try_from(value: String) -> Result<Self, self::error::ConversionError> {
247            value.parse()
248        }
249    }
250
251    ///SymbolTicker
252    ///
253    /// <details><summary>JSON schema</summary>
254    ///
255    /// ```json
256    ///{
257    ///  "type": "string",
258    ///  "enum": [
259    ///    "MOCK:XAUUSD"
260    ///  ]
261    ///}
262    /// ```
263    /// </details>
264    #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
265    pub enum SymbolTicker {
266        #[serde(rename = "MOCK:XAUUSD")]
267        MockXauusd,
268    }
269
270    impl From<&SymbolTicker> for SymbolTicker {
271        fn from(value: &SymbolTicker) -> Self {
272            value.clone()
273        }
274    }
275
276    impl ToString for SymbolTicker {
277        fn to_string(&self) -> String {
278            match *self {
279                Self::MockXauusd => "MOCK:XAUUSD".to_string(),
280            }
281        }
282    }
283
284    impl std::str::FromStr for SymbolTicker {
285        type Err = self::error::ConversionError;
286        fn from_str(value: &str) -> Result<Self, self::error::ConversionError> {
287            match value {
288                "MOCK:XAUUSD" => Ok(Self::MockXauusd),
289                _ => Err("invalid value".into()),
290            }
291        }
292    }
293
294    impl std::convert::TryFrom<&str> for SymbolTicker {
295        type Error = self::error::ConversionError;
296        fn try_from(value: &str) -> Result<Self, self::error::ConversionError> {
297            value.parse()
298        }
299    }
300
301    impl std::convert::TryFrom<&String> for SymbolTicker {
302        type Error = self::error::ConversionError;
303        fn try_from(value: &String) -> Result<Self, self::error::ConversionError> {
304            value.parse()
305        }
306    }
307
308    impl std::convert::TryFrom<String> for SymbolTicker {
309        type Error = self::error::ConversionError;
310        fn try_from(value: String) -> Result<Self, self::error::ConversionError> {
311            value.parse()
312        }
313    }
314}
315
316#[derive(Clone, Debug)]
317///Client for Trading API
318///
319///This API allow to interact with the trading system.
320///
321///
322///Version: 1.0.0
323pub struct Client {
324    pub(crate) baseurl: String,
325    pub(crate) client: reqwest::Client,
326}
327
328impl Client {
329    /// Create a new client.
330    ///
331    /// `baseurl` is the base URL provided to the internal
332    /// `reqwest::Client`, and should include a scheme and hostname,
333    /// as well as port and a path stem if applicable.
334    pub fn new(baseurl: &str) -> Self {
335        #[cfg(not(target_arch = "wasm32"))]
336        let client = {
337            let dur = std::time::Duration::from_secs(15);
338            reqwest::ClientBuilder::new()
339                .connect_timeout(dur)
340                .timeout(dur)
341        };
342        #[cfg(target_arch = "wasm32")]
343        let client = reqwest::ClientBuilder::new();
344        Self::new_with_client(baseurl, client.build().unwrap())
345    }
346
347    /// Construct a new client with an existing `reqwest::Client`,
348    /// allowing more control over its configuration.
349    ///
350    /// `baseurl` is the base URL provided to the internal
351    /// `reqwest::Client`, and should include a scheme and hostname,
352    /// as well as port and a path stem if applicable.
353    pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self {
354        Self {
355            baseurl: baseurl.to_string(),
356            client,
357        }
358    }
359
360    /// Get the base URL to which requests are made.
361    pub fn baseurl(&self) -> &String {
362        &self.baseurl
363    }
364
365    /// Get the internal `reqwest::Client` used to make requests.
366    pub fn client(&self) -> &reqwest::Client {
367        &self.client
368    }
369
370    /// Get the version of this API.
371    ///
372    /// This string is pulled directly from the source OpenAPI
373    /// document and may be in any format the API selects.
374    pub fn api_version(&self) -> &'static str {
375        "1.0.0"
376    }
377}
378
379#[allow(clippy::all)]
380impl Client {
381    ///Get the list of candles
382    ///
383    ///Sends a `GET` request to `/candles`
384    ///
385    ///Arguments:
386    /// - `from`: The start UNIX timestamp
387    /// - `resolution`: The resolution of the candles
388    /// - `symbol`: The symbol of the instrument
389    /// - `to`: The end UNIX timestamp
390    pub async fn get_candles<'a>(
391        &'a self,
392        from: Option<i64>,
393        resolution: types::Resolution,
394        symbol: types::SymbolTicker,
395        to: Option<i64>,
396    ) -> Result<ResponseValue<types::Candles>, Error<types::HkError>> {
397        let url = format!("{}/candles", self.baseurl,);
398        let mut query = Vec::with_capacity(4usize);
399        if let Some(v) = &from {
400            query.push(("from", v.to_string()));
401        }
402
403        query.push(("resolution", resolution.to_string()));
404        query.push(("symbol", symbol.to_string()));
405        if let Some(v) = &to {
406            query.push(("to", v.to_string()));
407        }
408
409        #[allow(unused_mut)]
410        let mut request = self
411            .client
412            .get(url)
413            .header(
414                reqwest::header::ACCEPT,
415                reqwest::header::HeaderValue::from_static("application/json"),
416            )
417            .query(&query)
418            .build()?;
419        let result = self.client.execute(request).await;
420        let response = result?;
421        match response.status().as_u16() {
422            200u16 => ResponseValue::from_response(response).await,
423            404u16 => Err(Error::ErrorResponse(
424                ResponseValue::from_response(response).await?,
425            )),
426            500u16 => Err(Error::ErrorResponse(
427                ResponseValue::from_response(response).await?,
428            )),
429            _ => Err(Error::UnexpectedResponse(response)),
430        }
431    }
432}
433
434/// Items consumers will typically use such as the Client.
435pub mod prelude {
436    #[allow(unused_imports)]
437    pub use super::Client;
438}