jquants_api_client/api/
indicies.rs

1//! Indices (OHLC) API.
2
3use serde::{Deserialize, Serialize};
4
5use super::{
6    shared::{
7        traits::{
8            builder::JQuantsBuilder,
9            pagination::{HasPaginationKey, MergePage, Paginatable},
10        },
11        types::index_code::IndexCode,
12    },
13    JQuantsApiClient, JQuantsPlanClient,
14};
15
16/// Builder for Indices (OHLC) API.
17#[derive(Clone, Serialize)]
18pub struct IndicesBuilder {
19    #[serde(skip)]
20    client: JQuantsApiClient,
21
22    /// Index code (e.g., "0000" or "0028")
23    #[serde(skip_serializing_if = "Option::is_none")]
24    code: Option<IndexCode>,
25    /// Starting point of data period (e.g., "20210901" or "2021-09-01")
26    #[serde(skip_serializing_if = "Option::is_none")]
27    from: Option<String>,
28    /// End point of data period (e.g., "20210907" or "2021-09-07")
29    #[serde(skip_serializing_if = "Option::is_none")]
30    to: Option<String>,
31    /// Date of data (e.g., "20210907" or "2021-09-07")
32    #[serde(skip_serializing_if = "Option::is_none")]
33    date: Option<String>,
34
35    /// Pagination key.
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pagination_key: Option<String>,
38}
39
40impl JQuantsBuilder<IndicesResponse> for IndicesBuilder {
41    async fn send(self) -> Result<IndicesResponse, crate::JQuantsError> {
42        self.send_ref().await
43    }
44
45    async fn send_ref(&self) -> Result<IndicesResponse, crate::JQuantsError> {
46        self.client.inner.get("indices", self).await
47    }
48}
49
50impl Paginatable<IndicesResponse> for IndicesBuilder {
51    fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
52        self.pagination_key = Some(pagination_key.into());
53        self
54    }
55}
56
57impl IndicesBuilder {
58    /// Create a new builder.
59    pub(crate) fn new(client: JQuantsApiClient) -> Self {
60        Self {
61            client,
62            code: None,
63            from: None,
64            to: None,
65            date: None,
66            pagination_key: None,
67        }
68    }
69
70    /// Set index code (e.g., "0000" or "0028")
71    pub fn code(mut self, code: impl Into<IndexCode>) -> Self {
72        self.code = Some(code.into());
73        self
74    }
75
76    /// Set starting point of data period (e.g., "20210901" or "2021-09-01")
77    pub fn from(mut self, from: impl Into<String>) -> Self {
78        self.from = Some(from.into());
79        self
80    }
81
82    /// Set end point of data period (e.g., "20210907" or "2021-09-07")
83    pub fn to(mut self, to: impl Into<String>) -> Self {
84        self.to = Some(to.into());
85        self
86    }
87
88    /// Set date of data (e.g., "20210907" or "2021-09-07")
89    pub fn date(mut self, date: impl Into<String>) -> Self {
90        self.date = Some(date.into());
91        self
92    }
93}
94
95/// Builder for Indices (OHLC) API.
96pub trait IndicesApi: JQuantsPlanClient {
97    /// Get API builder for Indices (OHLC).
98    ///
99    /// Use [Indices (OHLC) (/indices) API](https://jpx.gitbook.io/j-quants-en/api-reference/indices)
100    fn get_indices(&self) -> IndicesBuilder {
101        IndicesBuilder::new(self.get_api_client().clone())
102    }
103}
104
105/// Indices (OHLC) response.
106///
107/// See: [API Reference](https://jpx.gitbook.io/j-quants-en/api-reference/indices)
108#[derive(Debug, Clone, PartialEq, Deserialize)]
109pub struct IndicesResponse {
110    /// List of indices data
111    pub indices: Vec<IndexItem>,
112    /// Pagination key for fetching next set of data
113    pub pagination_key: Option<String>,
114}
115
116impl HasPaginationKey for IndicesResponse {
117    fn get_pagination_key(&self) -> Option<&str> {
118        self.pagination_key.as_deref()
119    }
120}
121
122impl MergePage for IndicesResponse {
123    fn merge_page(
124        page: Result<Vec<Self>, crate::JQuantsError>,
125    ) -> Result<Self, crate::JQuantsError> {
126        let mut page = page?;
127        let mut merged = page.pop().unwrap();
128        for p in page {
129            merged.indices.extend(p.indices);
130        }
131        merged.pagination_key = None;
132
133        Ok(merged)
134    }
135}
136
137/// Represents a single indices (OHLC) data item.
138#[derive(Debug, Clone, PartialEq, Deserialize)]
139pub struct IndexItem {
140    /// Trade date (YYYY-MM-DD)
141    #[serde(rename = "Date")]
142    pub date: String,
143
144    /// Index code
145    #[serde(rename = "Code")]
146    pub code: IndexCode,
147
148    /// Open Price
149    #[serde(rename = "Open")]
150    pub open: f64,
151
152    /// High Price
153    #[serde(rename = "High")]
154    pub high: f64,
155
156    /// Low Price
157    #[serde(rename = "Low")]
158    pub low: f64,
159
160    /// Close Price
161    #[serde(rename = "Close")]
162    pub close: f64,
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_deserialize_indices_response() {
171        let json = r#"
172            {
173                "indices": [
174                    {
175                        "Date": "2023-12-01",
176                        "Code": "0028",
177                        "Open": 1199.18,
178                        "High": 1202.58,
179                        "Low": 1195.01,
180                        "Close": 1200.17
181                    }
182                ],
183                "pagination_key": "value1.value2."
184            }
185        "#;
186
187        let response: IndicesResponse = serde_json::from_str(json).unwrap();
188        let expected_response = IndicesResponse {
189            indices: vec![IndexItem {
190                date: "2023-12-01".to_string(),
191                code: IndexCode::TOPIXCore30,
192                open: 1199.18,
193                high: 1202.58,
194                low: 1195.01,
195                close: 1200.17,
196            }],
197            pagination_key: Some("value1.value2.".to_string()),
198        };
199
200        pretty_assertions::assert_eq!(response, expected_response);
201    }
202
203    #[test]
204    fn test_deserialize_indices_response_no_pagination_key() {
205        let json = r#"
206            {
207                "indices": [
208                    {
209                        "Date": "2023-12-01",
210                        "Code": "0028",
211                        "Open": 1199.18,
212                        "High": 1202.58,
213                        "Low": 1195.01,
214                        "Close": 1200.17
215                    }
216                ]
217            }
218        "#;
219
220        let response: IndicesResponse = serde_json::from_str(json).unwrap();
221        let expected_response = IndicesResponse {
222            indices: vec![IndexItem {
223                date: "2023-12-01".to_string(),
224                code: IndexCode::TOPIXCore30,
225                open: 1199.18,
226                high: 1202.58,
227                low: 1195.01,
228                close: 1200.17,
229            }],
230            pagination_key: None,
231        };
232
233        pretty_assertions::assert_eq!(response, expected_response);
234    }
235
236    #[test]
237    fn test_deserialize_indices_response_multiple_items() {
238        let json = r#"
239            {
240                "indices": [
241                    {
242                        "Date": "2023-11-30",
243                        "Code": "0000",
244                        "Open": 1500.50,
245                        "High": 1520.75,
246                        "Low": 1495.00,
247                        "Close": 1510.25
248                    },
249                    {
250                        "Date": "2023-12-01",
251                        "Code": "0028",
252                        "Open": 1199.18,
253                        "High": 1202.58,
254                        "Low": 1195.01,
255                        "Close": 1200.17
256                    }
257                ],
258                "pagination_key": "value1.value2."
259            }
260        "#;
261
262        let response: IndicesResponse = serde_json::from_str(json).unwrap();
263        let expected_response = IndicesResponse {
264            indices: vec![
265                IndexItem {
266                    date: "2023-11-30".to_string(),
267                    code: IndexCode::TOPIX,
268                    open: 1500.50,
269                    high: 1520.75,
270                    low: 1495.00,
271                    close: 1510.25,
272                },
273                IndexItem {
274                    date: "2023-12-01".to_string(),
275                    code: IndexCode::TOPIXCore30,
276                    open: 1199.18,
277                    high: 1202.58,
278                    low: 1195.01,
279                    close: 1200.17,
280                },
281            ],
282            pagination_key: Some("value1.value2.".to_string()),
283        };
284
285        pretty_assertions::assert_eq!(response, expected_response);
286    }
287
288    #[test]
289    fn test_deserialize_indices_response_no_data() {
290        let json = r#"
291            {
292                "indices": []
293            }
294        "#;
295
296        let response: IndicesResponse = serde_json::from_str(json).unwrap();
297        let expected_response = IndicesResponse {
298            indices: vec![],
299            pagination_key: None,
300        };
301
302        pretty_assertions::assert_eq!(response, expected_response);
303    }
304}