jquants_api_client/api/
topic_prices.rs

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