Skip to main content

databento/reference/
adjustment.rs

1//! The adjustment factors API.
2
3use dbn::{Compression, SType};
4use serde::Deserialize;
5use time::{Date, OffsetDateTime};
6use tracing::instrument;
7
8use crate::{
9    deserialize::deserialize_date_time,
10    historical::{handle_zstd_jsonl_response, AddToForm},
11    reference::{AdjustmentStatus, Country, Currency, End, Event, Frequency, SecurityType, Start},
12    DateTimeLike, Symbols,
13};
14
15/// A client for the adjustment factors group of Reference API endpoints.
16#[derive(Debug)]
17pub struct AdjustmentFactorsClient<'a> {
18    pub(crate) inner: &'a mut super::Client,
19}
20
21impl AdjustmentFactorsClient<'_> {
22    /// Requests a new adjustment factor time series from Databento.
23    ///
24    /// # Errors
25    /// This function returns an error when it fails to communicate with the Databento API
26    /// or the API indicates there's an issue with the request.
27    #[instrument(name = "adjustment_factors.get_range")]
28    pub async fn get_range(
29        &mut self,
30        params: &GetRangeParams,
31    ) -> crate::Result<Vec<AdjustmentFactor>> {
32        let form = vec![
33            ("stype_in", params.stype_in.to_string()),
34            ("symbols", params.symbols.to_api_string()),
35            ("compression", Compression::Zstd.to_string()),
36        ]
37        .add_to_form(&Start(params.start))
38        .add_to_form(&End(params.end))
39        .add_to_form(&params.countries)
40        .add_to_form(&params.security_types);
41
42        let resp = self
43            .inner
44            .post("adjustment_factors.get_range")?
45            .form(&form)
46            .send()
47            .await?;
48        let mut adjustment_factors: Vec<AdjustmentFactor> =
49            handle_zstd_jsonl_response(resp).await?;
50        adjustment_factors.sort_by_key(|a| a.ex_date);
51        Ok(adjustment_factors)
52    }
53}
54
55/// The parameters for [`AdjustmentFactorsClient::get_range()`]. Use
56/// [`GetRangeParams::builder()`] to get a builder type with all the preset defaults.
57#[derive(Debug, Clone, bon::Builder, PartialEq, Eq)]
58pub struct GetRangeParams {
59    /// The inclusive start time of the request range. Filters on `index`.
60    #[builder(with = |dt: impl DateTimeLike| dt.to_date_time())]
61    pub start: OffsetDateTime,
62    /// The exclusive end time of the request range. Filters on `index`.
63    ///
64    /// If `None`, all data after `start` will be included in the response.
65    #[builder(with = |dt: impl DateTimeLike| dt.to_date_time())]
66    pub end: Option<OffsetDateTime>,
67    /// The symbols to filter for.
68    #[builder(into)]
69    pub symbols: Symbols,
70    /// The symbology type of the input `symbols`. Defaults to
71    /// [`RawSymbol`](SType::RawSymbol).
72    #[builder(default = SType::RawSymbol)]
73    pub stype_in: SType,
74    /// An optional list of country codes to filter for. By default all countries are
75    /// included.
76    #[builder(default, into)]
77    pub countries: Vec<Country>,
78    /// An optional list of security types to filter for. By default all security types
79    /// are included.
80    #[builder(default, into)]
81    pub security_types: Vec<SecurityType>,
82}
83
84/// A record in the adjustment factor response.
85#[derive(Debug, Clone, PartialEq, Deserialize)]
86pub struct AdjustmentFactor {
87    /*
88     * Identifiers
89     */
90    /// Security level numerical ID. Can be used to link all multiple listings together.
91    pub security_id: String,
92    /// Event identifier unique at the event level. Links to the corporate actions `event_id`.
93    pub event_id: String,
94    /// The event type.
95    pub event: Event,
96
97    /*
98     * Exchange
99     */
100    /// The issuer name.
101    pub issuer_name: String,
102    /// The security type.
103    pub security_type: SecurityType,
104    /// Exchange code for the primary security.
105    pub primary_exchange: Option<String>,
106    /// Exchange code for the listing. Equivalent to the MIC but more stable as MIC
107    /// might not be available in a timely fashion. Also note that the MIC can change
108    /// but the exchange will remain the same.
109    pub exchange: Option<String>,
110    /// Market Identifier Code (MIC) as an ISO 10383 string.
111    pub operating_mic: String,
112
113    /*
114     * Symbology
115     */
116    /// The query input symbol which matched the record.
117    pub symbol: Option<String>,
118    /// Nasdaq Integrated Platform Suffix convention symbol.
119    pub nasdaq_symbol: Option<String>,
120    /// Local Code. Usually unique at market level but there are exceptions to this
121    /// rule. Either an alpha string, or a number.
122    pub local_code: Option<String>,
123    /// Resultant local code when applicable/known.
124    pub local_code_resulting: Option<String>,
125    /// ISIN global level identifier as an ISO 6166 string.
126    pub isin: Option<String>,
127    /// Resultant ISIN when applicable/known.
128    pub isin_resulting: Option<String>,
129    /// US domestic CUSIP.
130    pub us_code: Option<String>,
131
132    /// The adjustment status.
133    pub status: AdjustmentStatus,
134    /// Date from which the event is effective.
135    pub ex_date: Date,
136    /// Adjustment factor to apply.
137    pub factor: f64,
138    /// Closing price on the `ex_date`.
139    pub close: Option<f64>,
140    /// Currency for the closing price.
141    pub currency: Option<String>,
142    /// Market sentiment - the market's reaction to the event. Simply the previous close
143    /// divided by today's open. Only correct if factor calculation requires previous close.
144    pub sentiment: f64,
145    /// Reason/type of event, used to distinguish between different event types.
146    pub reason: u32,
147    /// The amount of the dividend before any taxes or fees are deducted. This value
148    /// represents the total dividend declared by the company.
149    pub gross_dividend: Option<f64>,
150    /// The currency in which the dividend is paid.
151    pub dividend_currency: Option<Currency>,
152    /// The frequency at which the dividend is paid.
153    pub frequency: Option<Frequency>,
154    /// The choice or option number associated with the event, often used when
155    /// shareholders are given multiple options for how they would like to receive the
156    /// dividend or other corporate action benefit (either cash, or script).
157    pub option: u32,
158    /// A human-readable description of the event.
159    pub detail: String,
160    /// The timestamp (UTC) the record was added by Databento.
161    #[serde(deserialize_with = "deserialize_date_time")]
162    pub ts_created: OffsetDateTime,
163}
164
165#[cfg(test)]
166mod tests {
167    use std::io;
168
169    use reqwest::StatusCode;
170    use time::macros::{date, datetime};
171    use wiremock::{
172        matchers::{basic_auth, method, path},
173        Mock, MockServer, ResponseTemplate,
174    };
175
176    use super::*;
177    use crate::{
178        body_contains,
179        historical::{test_infra::API_KEY, API_VERSION},
180        reference::test_infra::client,
181    };
182
183    #[tokio::test]
184    async fn test_get_range() {
185        let _ = tracing_subscriber::FmtSubscriber::builder()
186            .with_test_writer()
187            .try_init();
188        let start = datetime!(2023- 10 - 10 00:00 UTC);
189
190        let bytes = zstd::encode_all(
191            io::Cursor::new(concat!(
192                r#"{"security_id": "S-1318698","#,
193                r#""event_id": "E-3287361-DIV","#,
194                r#""event": "DIV","#,
195                r#""issuer_name": "VanEck ETF Trust","#,
196                r#""security_type": "ETF","#,
197                r#""primary_exchange": "USBATS","#,
198                r#""exchange": "USBATS","#,
199                r#""operating_mic": "BATS","#,
200                r#""symbol": "HYD","#,
201                r#""nasdaq_symbol": "HYD","#,
202                r#""local_code": "HYD","#,
203                r#""local_code_resulting": null,"#,
204                r#""isin": "US92189H4092","#,
205                r#""isin_resulting": null,"#,
206                r#""us_code": "92189H409","#,
207                r#""status": "A","#,
208                r#""ex_date": "2024-05-01","#,
209                r#""factor": 0.995833170541121,"#,
210                r#""close": 51.19,"#,
211                r#""currency": "USD","#,
212                r#""sentiment": 0.998241844110178,"#,
213                r#""reason": 17,"#,
214                r#""gross_dividend": 0.2133,"#,
215                r#""dividend_currency": "USD","#,
216                r#""frequency": "MNT","#,
217                r#""option": 1,"#,
218                r#""detail": "INT Dividend (cash) of USD0.2133/ETF","#,
219                r#""ts_created": "1970-01-01T00:00:00.000000000Z"}
220"#,
221            )),
222            0,
223        )
224        .unwrap();
225
226        let mock_server = MockServer::start().await;
227        Mock::given(method("POST"))
228            .and(basic_auth(API_KEY, ""))
229            .and(path(format!(
230                "/v{API_VERSION}/adjustment_factors.get_range"
231            )))
232            .and(body_contains(
233                "start",
234                start.unix_timestamp_nanos().to_string(),
235            ))
236            .and(body_contains("stype_in", "raw_symbol"))
237            .and(body_contains("symbols", "MSFT"))
238            .and(body_contains("security_types", "EQS"))
239            .respond_with(ResponseTemplate::new(StatusCode::OK.as_u16()).set_body_bytes(bytes))
240            .mount(&mock_server)
241            .await;
242
243        let mut client = client(&mock_server);
244        let res = client
245            .adjustment_factors()
246            .get_range(
247                &GetRangeParams::builder()
248                    .start(start)
249                    .security_types([SecurityType::Eqs])
250                    .countries([Country::Us])
251                    .symbols("MSFT")
252                    .build(),
253            )
254            .await
255            .unwrap();
256        assert_eq!(res.len(), 1);
257        let res = &res[0];
258        assert_eq!(res.event, Event::Div);
259        assert_eq!(res.security_type, SecurityType::Etf);
260        assert_eq!(res.status, AdjustmentStatus::Apply);
261        assert_eq!(res.ex_date, date!(2024 - 05 - 01));
262        assert_eq!(res.dividend_currency, Some(Currency::Usd));
263        assert_eq!(res.frequency, Some(Frequency::Monthly));
264    }
265}