jquants_api_client/api/
cash_dividend_data.rs

1//! Cash Dividend Data (/fins/dividend) API.
2
3use serde::{Deserialize, Serialize};
4
5use super::{
6    shared::{
7        traits::{
8            builder::JQuantsBuilder,
9            pagination::{HasPaginationKey, MergePage, Paginatable},
10        },
11        types::{
12            amount_per_share::AmountPerShare,
13            dividend::{
14                DevidendStatucCode, DividendCommemorativeSpecialCode, DividendForecastResultCode,
15                DividendInterimFinalCode,
16            },
17            payable_date::PayableDate,
18        },
19    },
20    JQuantsApiClient, JQuantsPlanClient,
21};
22
23/// Builder for Cash Dividend Data API.
24#[derive(Clone, Serialize)]
25pub struct CashDividendDataBuilder {
26    #[serde(skip)]
27    client: JQuantsApiClient,
28
29    /// Issue code (e.g., "27800" or "2780")
30    #[serde(skip_serializing_if = "Option::is_none")]
31    code: Option<String>,
32
33    /// Disclosure date (e.g., "20210901" or "2021-09-01")
34    #[serde(skip_serializing_if = "Option::is_none")]
35    date: Option<String>,
36
37    /// Starting point of data period (e.g., "20210901" or "2021-09-01")
38    #[serde(skip_serializing_if = "Option::is_none")]
39    from: Option<String>,
40
41    /// End point of data period (e.g., "20210907" or "2021-09-07")
42    #[serde(skip_serializing_if = "Option::is_none")]
43    to: Option<String>,
44
45    /// Pagination key.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pagination_key: Option<String>,
48}
49
50impl JQuantsBuilder<CashDividendDataResponse> for CashDividendDataBuilder {
51    async fn send(self) -> Result<CashDividendDataResponse, crate::JQuantsError> {
52        self.send_ref().await
53    }
54
55    async fn send_ref(&self) -> Result<CashDividendDataResponse, crate::JQuantsError> {
56        self.client.inner.get("fins/dividend", self).await
57    }
58}
59
60impl Paginatable<CashDividendDataResponse> for CashDividendDataBuilder {
61    fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
62        self.pagination_key = Some(pagination_key.into());
63        self
64    }
65}
66
67impl CashDividendDataBuilder {
68    /// Create a new builder.
69    pub(crate) fn new(client: JQuantsApiClient) -> Self {
70        Self {
71            client,
72            code: None,
73            date: None,
74            from: None,
75            to: None,
76            pagination_key: None,
77        }
78    }
79
80    /// Set issue code (e.g., "27800" or "2780")
81    pub fn code(mut self, code: impl Into<String>) -> Self {
82        self.code = Some(code.into());
83        self
84    }
85
86    /// Set disclosure date (e.g., "20210901" or "2021-09-01")
87    pub fn date(mut self, date: impl Into<String>) -> Self {
88        self.date = Some(date.into());
89        self
90    }
91
92    /// Set starting point of data period (e.g., "20210901" or "2021-09-01")
93    pub fn from(mut self, from: impl Into<String>) -> Self {
94        self.from = Some(from.into());
95        self
96    }
97
98    /// Set end point of data period (e.g., "20210907" or "2021-09-07")
99    pub fn to(mut self, to: impl Into<String>) -> Self {
100        self.to = Some(to.into());
101        self
102    }
103}
104
105/// Trait for Cash Dividend Data API.
106pub trait CashDividendDataApi: JQuantsPlanClient {
107    /// Get API builder for Cash Dividend Data.
108    ///
109    /// Use [Cash Dividend Data (/fins/dividend) API](https://jpx.gitbook.io/j-quants-en/api-reference/dividend)
110    fn get_cash_dividend_data(&self) -> CashDividendDataBuilder {
111        CashDividendDataBuilder::new(self.get_api_client().clone())
112    }
113}
114
115/// Cash Dividend Data API response.
116///
117/// See: [API Reference](https://jpx.gitbook.io/j-quants-en/api-reference/dividend)
118#[derive(Debug, Clone, PartialEq, Deserialize)]
119pub struct CashDividendDataResponse {
120    /// List of cash dividend data
121    pub dividend: Vec<CashDividendItem>,
122    /// Pagination key for fetching next set of data
123    pub pagination_key: Option<String>,
124}
125
126impl HasPaginationKey for CashDividendDataResponse {
127    fn get_pagination_key(&self) -> Option<&str> {
128        self.pagination_key.as_deref()
129    }
130}
131
132impl MergePage for CashDividendDataResponse {
133    fn merge_page(
134        page: Result<Vec<Self>, crate::JQuantsError>,
135    ) -> Result<Self, crate::JQuantsError> {
136        let mut page = page?;
137        let mut merged = page.pop().unwrap();
138        for p in page {
139            merged.dividend.extend(p.dividend);
140        }
141        merged.pagination_key = None;
142
143        Ok(merged)
144    }
145}
146
147/// Represents a single cash dividend data item.
148#[derive(Debug, Clone, PartialEq, Deserialize)]
149pub struct CashDividendItem {
150    /// Announcement Date (YYYY-MM-DD)
151    #[serde(rename = "AnnouncementDate")]
152    pub announcement_date: String,
153
154    /// Announcement Time (HH:MM)
155    #[serde(rename = "AnnouncementTime")]
156    pub announcement_time: String,
157
158    /// Issue Code (5-character)
159    #[serde(rename = "Code")]
160    pub code: String,
161
162    /// Reference Number
163    #[serde(rename = "ReferenceNumber")]
164    pub reference_number: String,
165
166    /// Status Code
167    #[serde(rename = "StatusCode")]
168    pub status_code: DevidendStatucCode,
169
170    /// Board Meeting Date (YYYY-MM-DD)
171    #[serde(rename = "BoardMeetingDate")]
172    pub board_meeting_date: String,
173
174    /// Interim/Final Code
175    #[serde(rename = "InterimFinalCode")]
176    pub interim_final_code: DividendInterimFinalCode,
177
178    /// Forecast/Result Code
179    #[serde(rename = "ForecastResultCode")]
180    pub forecast_result_code: DividendForecastResultCode,
181
182    /// Interim Final Term
183    #[serde(rename = "InterimFinalTerm")]
184    pub interim_final_term: String,
185
186    /// Gross Dividend Rate
187    #[serde(rename = "GrossDividendRate")]
188    pub gross_dividend_rate: AmountPerShare,
189
190    /// Record Date (YYYY-MM-DD)
191    #[serde(rename = "RecordDate")]
192    pub record_date: String,
193
194    /// Ex-Rights Date (YYYY-MM-DD)
195    #[serde(rename = "ExDate")]
196    pub ex_date: String,
197
198    /// Actual Record Date (YYYY-MM-DD)
199    #[serde(rename = "ActualRecordDate")]
200    pub actual_record_date: String,
201
202    /// Payable Date (YYYY-MM-DD)
203    #[serde(rename = "PayableDate")]
204    pub payable_date: PayableDate,
205
206    /// CA Reference Number
207    #[serde(rename = "CAReferenceNumber")]
208    pub ca_reference_number: String,
209
210    /// Distribution Amount per Share
211    #[serde(rename = "DistributionAmount")]
212    pub distribution_amount: AmountPerShare,
213
214    /// Retained Earnings per Share
215    #[serde(rename = "RetainedEarnings")]
216    pub retained_earnings: AmountPerShare,
217
218    /// Deemed Dividend per Share
219    #[serde(rename = "DeemedDividend")]
220    pub deemed_dividend: AmountPerShare,
221
222    /// Deemed Capital Gains per Share
223    #[serde(rename = "DeemedCapitalGains")]
224    pub deemed_capital_gains: AmountPerShare,
225
226    /// Net Asset Decrease Ratio
227    #[serde(rename = "NetAssetDecreaseRatio")]
228    pub net_asset_decrease_ratio: AmountPerShare,
229
230    /// Commemorative/Special Code
231    #[serde(rename = "CommemorativeSpecialCode")]
232    pub commemorative_special_code: DividendCommemorativeSpecialCode,
233
234    /// Commemorative Dividend Rate per Share
235    #[serde(rename = "CommemorativeDividendRate")]
236    pub commemorative_dividend_rate: AmountPerShare,
237
238    /// Special Dividend Rate per Share
239    #[serde(rename = "SpecialDividendRate")]
240    pub special_dividend_rate: AmountPerShare,
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_deserialize_cash_dividend_data_response() {
249        let json_data = r#"
250        {
251            "dividend": [
252                {
253                    "AnnouncementDate": "2014-02-24",
254                    "AnnouncementTime": "09:21",
255                    "Code": "15550",
256                    "ReferenceNumber": "201402241B00002",
257                    "StatusCode": "1",
258                    "BoardMeetingDate": "2014-02-24",
259                    "InterimFinalCode": "2",
260                    "ForecastResultCode": "2",
261                    "InterimFinalTerm": "2014-03",
262                    "GrossDividendRate": "-",
263                    "RecordDate": "2014-03-10",
264                    "ExDate": "2014-03-06",
265                    "ActualRecordDate": "2014-03-10",
266                    "PayableDate": "-",
267                    "CAReferenceNumber": "201402241B00002",
268                    "DistributionAmount": "",
269                    "RetainedEarnings": "",
270                    "DeemedDividend": "",
271                    "DeemedCapitalGains": "",
272                    "NetAssetDecreaseRatio": "",
273                    "CommemorativeSpecialCode": "0",
274                    "CommemorativeDividendRate": "",
275                    "SpecialDividendRate": ""
276                }
277            ],
278            "pagination_key": "value1.value2."
279        }
280        "#;
281
282        let response: CashDividendDataResponse = serde_json::from_str(json_data).unwrap();
283
284        let expected_dividend = vec![CashDividendItem {
285            announcement_date: "2014-02-24".to_string(),
286            announcement_time: "09:21".to_string(),
287            code: "15550".to_string(),
288            reference_number: "201402241B00002".to_string(),
289            status_code: DevidendStatucCode::New,
290            board_meeting_date: "2014-02-24".to_string(),
291            interim_final_code: DividendInterimFinalCode::Final,
292            forecast_result_code: DividendForecastResultCode::Forecast,
293            interim_final_term: "2014-03".to_string(),
294            gross_dividend_rate: AmountPerShare::Undetermined,
295            record_date: "2014-03-10".to_string(),
296            ex_date: "2014-03-06".to_string(),
297            actual_record_date: "2014-03-10".to_string(),
298            payable_date: PayableDate::Undetermined,
299            ca_reference_number: "201402241B00002".to_string(),
300            distribution_amount: AmountPerShare::NotApplicable,
301            retained_earnings: AmountPerShare::NotApplicable,
302            deemed_dividend: AmountPerShare::NotApplicable,
303            deemed_capital_gains: AmountPerShare::NotApplicable,
304            net_asset_decrease_ratio: AmountPerShare::NotApplicable,
305            commemorative_special_code: DividendCommemorativeSpecialCode::Normal,
306            commemorative_dividend_rate: AmountPerShare::NotApplicable,
307            special_dividend_rate: AmountPerShare::NotApplicable,
308        }];
309
310        let expected_response = CashDividendDataResponse {
311            dividend: expected_dividend,
312            pagination_key: Some("value1.value2.".to_string()),
313        };
314
315        pretty_assertions::assert_eq!(response, expected_response);
316    }
317
318    #[test]
319    fn test_deserialize_cash_dividend_data_response_no_pagination_key() {
320        let json_data = r#"
321        {
322            "dividend": [
323                {
324                    "AnnouncementDate": "2014-02-24",
325                    "AnnouncementTime": "09:21",
326                    "Code": "15550",
327                    "ReferenceNumber": "201402241B00002",
328                    "StatusCode": "1",
329                    "BoardMeetingDate": "2014-02-24",
330                    "InterimFinalCode": "2",
331                    "ForecastResultCode": "2",
332                    "InterimFinalTerm": "2014-03",
333                    "GrossDividendRate": "-",
334                    "RecordDate": "2014-03-10",
335                    "ExDate": "2014-03-06",
336                    "ActualRecordDate": "2014-03-10",
337                    "PayableDate": "-",
338                    "CAReferenceNumber": "201402241B00002",
339                    "DistributionAmount": "",
340                    "RetainedEarnings": "",
341                    "DeemedDividend": "",
342                    "DeemedCapitalGains": "",
343                    "NetAssetDecreaseRatio": "",
344                    "CommemorativeSpecialCode": "0",
345                    "CommemorativeDividendRate": "",
346                    "SpecialDividendRate": ""
347                }
348            ]
349        }
350        "#;
351
352        let response: CashDividendDataResponse = serde_json::from_str(json_data).unwrap();
353
354        let expected_dividend = vec![CashDividendItem {
355            announcement_date: "2014-02-24".to_string(),
356            announcement_time: "09:21".to_string(),
357            code: "15550".to_string(),
358            reference_number: "201402241B00002".to_string(),
359            status_code: DevidendStatucCode::New,
360            board_meeting_date: "2014-02-24".to_string(),
361            interim_final_code: DividendInterimFinalCode::Final,
362            forecast_result_code: DividendForecastResultCode::Forecast,
363            interim_final_term: "2014-03".to_string(),
364            gross_dividend_rate: AmountPerShare::Undetermined,
365            record_date: "2014-03-10".to_string(),
366            ex_date: "2014-03-06".to_string(),
367            actual_record_date: "2014-03-10".to_string(),
368            payable_date: PayableDate::Undetermined,
369            ca_reference_number: "201402241B00002".to_string(),
370            distribution_amount: AmountPerShare::NotApplicable,
371            retained_earnings: AmountPerShare::NotApplicable,
372            deemed_dividend: AmountPerShare::NotApplicable,
373            deemed_capital_gains: AmountPerShare::NotApplicable,
374            net_asset_decrease_ratio: AmountPerShare::NotApplicable,
375            commemorative_special_code: DividendCommemorativeSpecialCode::Normal,
376            commemorative_dividend_rate: AmountPerShare::NotApplicable,
377            special_dividend_rate: AmountPerShare::NotApplicable,
378        }];
379
380        let expected_response = CashDividendDataResponse {
381            dividend: expected_dividend,
382            pagination_key: None,
383        };
384
385        pretty_assertions::assert_eq!(response, expected_response);
386    }
387
388    #[test]
389    fn test_deserialize_cash_dividend_data_response_multiple_items() {
390        let json_data = r#"
391        {
392            "dividend": [
393                {
394                    "AnnouncementDate": "2023-03-06",
395                    "AnnouncementTime": "10:00",
396                    "Code": "86970",
397                    "ReferenceNumber": "1",
398                    "StatusCode": "1",
399                    "BoardMeetingDate": "2023-03-06",
400                    "InterimFinalCode": "1",
401                    "ForecastResultCode": "1",
402                    "InterimFinalTerm": "2023-04",
403                    "GrossDividendRate": "100",
404                    "RecordDate": "2023-03-10",
405                    "ExDate": "2023-03-05",
406                    "ActualRecordDate": "2023-03-10",
407                    "PayableDate": "2023-03-15",
408                    "CAReferenceNumber": "1",
409                    "DistributionAmount": "100",
410                    "RetainedEarnings": "50",
411                    "DeemedDividend": "0",
412                    "DeemedCapitalGains": "0",
413                    "NetAssetDecreaseRatio": "0.05",
414                    "CommemorativeSpecialCode": "0",
415                    "CommemorativeDividendRate": "-",
416                    "SpecialDividendRate": "-"
417                },
418                {
419                    "AnnouncementDate": "2023-03-07",
420                    "AnnouncementTime": "11:00",
421                    "Code": "86970",
422                    "ReferenceNumber": "2",
423                    "StatusCode": "2",
424                    "BoardMeetingDate": "2023-03-07",
425                    "InterimFinalCode": "2",
426                    "ForecastResultCode": "1",
427                    "InterimFinalTerm": "2023-04",
428                    "GrossDividendRate": "110",
429                    "RecordDate": "2023-03-12",
430                    "ExDate": "2023-03-07",
431                    "ActualRecordDate": "2023-03-12",
432                    "PayableDate": "2023-03-17",
433                    "CAReferenceNumber": "1",
434                    "DistributionAmount": "110",
435                    "RetainedEarnings": "55",
436                    "DeemedDividend": "0",
437                    "DeemedCapitalGains": "0",
438                    "NetAssetDecreaseRatio": "0.055",
439                    "CommemorativeSpecialCode": "1",
440                    "CommemorativeDividendRate": "10",
441                    "SpecialDividendRate": "-"
442                }
443            ],
444            "pagination_key": "value3.value4."
445        }
446        "#;
447
448        let response: CashDividendDataResponse = serde_json::from_str(json_data).unwrap();
449
450        let expected_dividend = vec![
451            CashDividendItem {
452                announcement_date: "2023-03-06".to_string(),
453                announcement_time: "10:00".to_string(),
454                code: "86970".to_string(),
455                reference_number: "1".to_string(),
456                status_code: DevidendStatucCode::New,
457                board_meeting_date: "2023-03-06".to_string(),
458                interim_final_code: DividendInterimFinalCode::Interim,
459                forecast_result_code: DividendForecastResultCode::Determined,
460                interim_final_term: "2023-04".to_string(),
461                gross_dividend_rate: AmountPerShare::Number(100.0),
462                record_date: "2023-03-10".to_string(),
463                ex_date: "2023-03-05".to_string(),
464                actual_record_date: "2023-03-10".to_string(),
465                payable_date: PayableDate::Date("2023-03-15".to_string()),
466                ca_reference_number: "1".to_string(),
467                distribution_amount: AmountPerShare::Number(100.0),
468                retained_earnings: AmountPerShare::Number(50.0),
469                deemed_dividend: AmountPerShare::Number(0.0),
470                deemed_capital_gains: AmountPerShare::Number(0.0),
471                net_asset_decrease_ratio: AmountPerShare::Number(0.05),
472                commemorative_special_code: DividendCommemorativeSpecialCode::Normal,
473                commemorative_dividend_rate: AmountPerShare::Undetermined,
474                special_dividend_rate: AmountPerShare::Undetermined,
475            },
476            CashDividendItem {
477                announcement_date: "2023-03-07".to_string(),
478                announcement_time: "11:00".to_string(),
479                code: "86970".to_string(),
480                reference_number: "2".to_string(),
481                status_code: DevidendStatucCode::Revised,
482                board_meeting_date: "2023-03-07".to_string(),
483                interim_final_code: DividendInterimFinalCode::Final,
484                forecast_result_code: DividendForecastResultCode::Determined,
485                interim_final_term: "2023-04".to_string(),
486                gross_dividend_rate: AmountPerShare::Number(110.0),
487                record_date: "2023-03-12".to_string(),
488                ex_date: "2023-03-07".to_string(),
489                actual_record_date: "2023-03-12".to_string(),
490                payable_date: PayableDate::Date("2023-03-17".to_string()),
491                ca_reference_number: "1".to_string(),
492                distribution_amount: AmountPerShare::Number(110.0),
493                retained_earnings: AmountPerShare::Number(55.0),
494                deemed_dividend: AmountPerShare::Number(0.0),
495                deemed_capital_gains: AmountPerShare::Number(0.0),
496                net_asset_decrease_ratio: AmountPerShare::Number(0.055),
497                commemorative_special_code: DividendCommemorativeSpecialCode::Commemorative,
498                commemorative_dividend_rate: AmountPerShare::Number(10.0),
499                special_dividend_rate: AmountPerShare::Undetermined,
500            },
501        ];
502
503        let expected_response = CashDividendDataResponse {
504            dividend: expected_dividend,
505            pagination_key: Some("value3.value4.".to_string()),
506        };
507
508        pretty_assertions::assert_eq!(response, expected_response);
509    }
510
511    #[test]
512    fn test_deserialize_cash_dividend_data_response_no_data() {
513        let json_data = r#"
514        {
515            "dividend": []
516        }
517        "#;
518
519        let response: CashDividendDataResponse = serde_json::from_str(json_data).unwrap();
520        let expected_response = CashDividendDataResponse {
521            dividend: vec![],
522            pagination_key: None,
523        };
524
525        pretty_assertions::assert_eq!(response, expected_response);
526    }
527}