Skip to main content

longbridge/calendar/
context.rs

1use std::sync::Arc;
2
3use longbridge_httpcli::{HttpClient, Json, Method};
4use serde::Serialize;
5use tracing::{Subscriber, dispatcher, instrument::WithSubscriber};
6
7use crate::{Config, Result, calendar::types::*};
8
9struct InnerCalendarContext {
10    http_cli: HttpClient,
11    log_subscriber: Arc<dyn Subscriber + Send + Sync>,
12}
13
14impl Drop for InnerCalendarContext {
15    fn drop(&mut self) {
16        dispatcher::with_default(&self.log_subscriber.clone().into(), || {
17            tracing::info!("calendar context dropped");
18        });
19    }
20}
21
22/// Financial calendar context — earnings, dividends, splits, IPOs, macro data.
23#[derive(Clone)]
24pub struct CalendarContext(Arc<InnerCalendarContext>);
25
26impl CalendarContext {
27    /// Create a [`CalendarContext`]
28    pub fn new(config: Arc<Config>) -> Self {
29        let log_subscriber = config.create_log_subscriber("calendar");
30        dispatcher::with_default(&log_subscriber.clone().into(), || {
31            tracing::info!(language = ?config.language, "creating calendar context");
32        });
33        let ctx = Self(Arc::new(InnerCalendarContext {
34            http_cli: config.create_http_client(),
35            log_subscriber,
36        }));
37        dispatcher::with_default(&ctx.0.log_subscriber.clone().into(), || {
38            tracing::info!("calendar context created");
39        });
40        ctx
41    }
42
43    /// Returns the log subscriber
44    #[inline]
45    pub fn log_subscriber(&self) -> Arc<dyn Subscriber + Send + Sync> {
46        self.0.log_subscriber.clone()
47    }
48
49    /// Get financial calendar events.
50    ///
51    /// The endpoint is paginated via `next_date`. When the returned
52    /// `next_date` is non-empty, pass it as `start` to fetch the next page.
53    ///
54    /// Path: `GET /v1/quote/finance_calendar`
55    pub async fn finance_calendar(
56        &self,
57        category: CalendarCategory,
58        start: impl Into<String>,
59        end: impl Into<String>,
60        market: Option<String>,
61    ) -> Result<CalendarEventsResponse> {
62        let cat_str = match category {
63            CalendarCategory::Report => "report",
64            CalendarCategory::Dividend => "dividend",
65            CalendarCategory::Split => "split",
66            CalendarCategory::Ipo => "ipo",
67            CalendarCategory::MacroData => "macrodata",
68            CalendarCategory::Closed => "closed",
69            CalendarCategory::Meeting => "meeting",
70            CalendarCategory::Merge => "merge",
71        };
72        #[derive(Serialize)]
73        struct Query {
74            date: String,
75            date_end: String,
76            #[serde(rename = "types[]")]
77            types: &'static str,
78            #[serde(rename = "markets[]", skip_serializing_if = "Option::is_none")]
79            markets: Option<String>,
80        }
81        Ok(self
82            .0
83            .http_cli
84            .request(Method::GET, "/v1/quote/finance_calendar")
85            .query_params(Query {
86                date: start.into(),
87                date_end: end.into(),
88                types: cat_str,
89                markets: market,
90            })
91            .response::<Json<CalendarEventsResponse>>()
92            .send()
93            .with_subscriber(self.0.log_subscriber.clone())
94            .await?
95            .0)
96    }
97}