1use std::sync::Arc;
2
3use longbridge_httpcli::{HttpClient, Json, Method};
4use serde::{Serialize, de::DeserializeOwned};
5use tracing::{Subscriber, dispatcher, instrument::WithSubscriber};
6
7use crate::{
8 Config, Result,
9 market::types::*,
10 utils::counter::{index_symbol_to_counter_id, symbol_to_counter_id},
11};
12
13struct InnerMarketContext {
14 http_cli: HttpClient,
15 log_subscriber: Arc<dyn Subscriber + Send + Sync>,
16}
17
18impl Drop for InnerMarketContext {
19 fn drop(&mut self) {
20 dispatcher::with_default(&self.log_subscriber.clone().into(), || {
21 tracing::info!("market context dropped");
22 });
23 }
24}
25
26#[derive(Clone)]
29pub struct MarketContext(Arc<InnerMarketContext>);
30
31impl MarketContext {
32 pub fn new(config: Arc<Config>) -> Self {
34 let log_subscriber = config.create_log_subscriber("market");
35 dispatcher::with_default(&log_subscriber.clone().into(), || {
36 tracing::info!(language = ?config.language, "creating market context");
37 });
38 let ctx = Self(Arc::new(InnerMarketContext {
39 http_cli: config.create_http_client(),
40 log_subscriber,
41 }));
42 dispatcher::with_default(&ctx.0.log_subscriber.clone().into(), || {
43 tracing::info!("market context created");
44 });
45 ctx
46 }
47
48 #[inline]
50 pub fn log_subscriber(&self) -> Arc<dyn Subscriber + Send + Sync> {
51 self.0.log_subscriber.clone()
52 }
53
54 async fn get<R, Q>(&self, path: &'static str, query: Q) -> Result<R>
55 where
56 R: DeserializeOwned + Send + Sync + 'static,
57 Q: Serialize + Send + Sync,
58 {
59 Ok(self
60 .0
61 .http_cli
62 .request(Method::GET, path)
63 .query_params(query)
64 .response::<Json<R>>()
65 .send()
66 .with_subscriber(self.0.log_subscriber.clone())
67 .await?
68 .0)
69 }
70
71 pub async fn market_status(&self) -> Result<MarketStatusResponse> {
77 #[derive(Serialize)]
78 struct Empty {}
79 self.get("/v1/quote/market-status", Empty {}).await
80 }
81
82 pub async fn broker_holding(
88 &self,
89 symbol: impl Into<String>,
90 period: BrokerHoldingPeriod,
91 ) -> Result<BrokerHoldingTop> {
92 let period_str = match period {
93 BrokerHoldingPeriod::Rct1 => "rct_1",
94 BrokerHoldingPeriod::Rct5 => "rct_5",
95 BrokerHoldingPeriod::Rct20 => "rct_20",
96 BrokerHoldingPeriod::Rct60 => "rct_60",
97 };
98 #[derive(Serialize)]
99 struct Query {
100 counter_id: String,
101 #[serde(rename = "type")]
102 period: &'static str,
103 }
104 self.get(
105 "/v1/quote/broker-holding",
106 Query {
107 counter_id: symbol_to_counter_id(&symbol.into()),
108 period: period_str,
109 },
110 )
111 .await
112 }
113
114 pub async fn broker_holding_detail(
118 &self,
119 symbol: impl Into<String>,
120 ) -> Result<BrokerHoldingDetail> {
121 #[derive(Serialize)]
122 struct Query {
123 counter_id: String,
124 }
125 self.get(
126 "/v1/quote/broker-holding/detail",
127 Query {
128 counter_id: symbol_to_counter_id(&symbol.into()),
129 },
130 )
131 .await
132 }
133
134 pub async fn broker_holding_daily(
138 &self,
139 symbol: impl Into<String>,
140 broker_id: impl Into<String>,
141 ) -> Result<BrokerHoldingDailyHistory> {
142 #[derive(Serialize)]
143 struct Query {
144 counter_id: String,
145 parti_number: String,
146 }
147 self.get(
148 "/v1/quote/broker-holding/daily",
149 Query {
150 counter_id: symbol_to_counter_id(&symbol.into()),
151 parti_number: broker_id.into(),
152 },
153 )
154 .await
155 }
156
157 pub async fn ah_premium(
163 &self,
164 symbol: impl Into<String>,
165 period: AhPremiumPeriod,
166 count: u32,
167 ) -> Result<AhPremiumKlines> {
168 #[derive(Serialize)]
169 struct Query {
170 counter_id: String,
171 line_type: &'static str,
172 line_num: u32,
173 }
174 self.get(
175 "/v1/quote/ahpremium/klines",
176 Query {
177 counter_id: symbol_to_counter_id(&symbol.into()),
178 line_type: period.to_line_type(),
179 line_num: count,
180 },
181 )
182 .await
183 }
184
185 pub async fn ah_premium_intraday(
189 &self,
190 symbol: impl Into<String>,
191 ) -> Result<AhPremiumIntraday> {
192 #[derive(Serialize)]
193 struct Query {
194 counter_id: String,
195 days: &'static str,
196 }
197 self.get(
198 "/v1/quote/ahpremium/timeshares",
199 Query {
200 counter_id: symbol_to_counter_id(&symbol.into()),
201 days: "1",
202 },
203 )
204 .await
205 }
206
207 pub async fn trade_stats(&self, symbol: impl Into<String>) -> Result<TradeStatsResponse> {
213 #[derive(Serialize)]
214 struct Query {
215 counter_id: String,
216 }
217 self.get(
218 "/v1/quote/trades-statistics",
219 Query {
220 counter_id: symbol_to_counter_id(&symbol.into()),
221 },
222 )
223 .await
224 }
225
226 pub async fn anomaly(&self, market: impl Into<String>) -> Result<AnomalyResponse> {
232 #[derive(Serialize)]
233 struct Query {
234 market: String,
235 category: &'static str,
236 }
237 self.get(
238 "/v1/quote/changes",
239 Query {
240 market: market.into().to_uppercase(),
241 category: "0",
242 },
243 )
244 .await
245 }
246
247 pub async fn constituent(&self, symbol: impl Into<String>) -> Result<IndexConstituents> {
255 #[derive(Serialize)]
256 struct Query {
257 counter_id: String,
258 }
259 self.get(
260 "/v1/quote/index-constituents",
261 Query {
262 counter_id: index_symbol_to_counter_id(&symbol.into()),
263 },
264 )
265 .await
266 }
267}