longbridge/screener/
context.rs1use std::sync::Arc;
2
3use longbridge_httpcli::{HttpClient, Json, Method};
4use serde::{Serialize, de::DeserializeOwned};
5use tracing::{Subscriber, dispatcher, instrument::WithSubscriber};
6
7use crate::{Config, Result, screener::types::*};
8
9struct InnerScreenerContext {
10 http_cli: HttpClient,
11 log_subscriber: Arc<dyn Subscriber + Send + Sync>,
12}
13
14impl Drop for InnerScreenerContext {
15 fn drop(&mut self) {
16 dispatcher::with_default(&self.log_subscriber.clone().into(), || {
17 tracing::info!("screener context dropped");
18 });
19 }
20}
21
22#[derive(Clone)]
24pub struct ScreenerContext(Arc<InnerScreenerContext>);
25
26impl ScreenerContext {
27 pub fn new(config: Arc<Config>) -> Self {
29 let log_subscriber = config.create_log_subscriber("screener");
30 dispatcher::with_default(&log_subscriber.clone().into(), || {
31 tracing::info!(language = ?config.language, "creating screener context");
32 });
33 let ctx = Self(Arc::new(InnerScreenerContext {
34 http_cli: config.create_http_client(),
35 log_subscriber,
36 }));
37 dispatcher::with_default(&ctx.0.log_subscriber.clone().into(), || {
38 tracing::info!("screener context created");
39 });
40 ctx
41 }
42
43 #[inline]
45 pub fn log_subscriber(&self) -> Arc<dyn Subscriber + Send + Sync> {
46 self.0.log_subscriber.clone()
47 }
48
49 async fn get<R, Q>(&self, path: &'static str, query: Q) -> Result<R>
50 where
51 R: DeserializeOwned + Send + Sync + 'static,
52 Q: Serialize + Send + Sync,
53 {
54 Ok(self
55 .0
56 .http_cli
57 .request(Method::GET, path)
58 .query_params(query)
59 .response::<Json<R>>()
60 .send()
61 .with_subscriber(self.0.log_subscriber.clone())
62 .await?
63 .0)
64 }
65
66 async fn post<R, B>(&self, path: &'static str, body: B) -> Result<R>
67 where
68 R: DeserializeOwned + Send + Sync + 'static,
69 B: std::fmt::Debug + Serialize + Send + Sync + 'static,
70 {
71 Ok(self
72 .0
73 .http_cli
74 .request(Method::POST, path)
75 .body(Json(body))
76 .response::<Json<R>>()
77 .send()
78 .with_subscriber(self.0.log_subscriber.clone())
79 .await?
80 .0)
81 }
82
83 pub async fn screener_recommend_strategies(
89 &self,
90 ) -> Result<ScreenerRecommendStrategiesResponse> {
91 #[derive(Serialize)]
92 struct Empty {}
93 let raw: serde_json::Value = self
94 .get("/v1/quote/screener/strategies/recommend", Empty {})
95 .await?;
96 Ok(ScreenerRecommendStrategiesResponse { data: raw })
97 }
98
99 pub async fn screener_user_strategies(&self) -> Result<ScreenerUserStrategiesResponse> {
105 #[derive(Serialize)]
106 struct Empty {}
107 let raw: serde_json::Value = self
108 .get("/v1/quote/screener/strategies/mine", Empty {})
109 .await?;
110 Ok(ScreenerUserStrategiesResponse { data: raw })
111 }
112
113 pub async fn screener_strategy(&self, id: i64) -> Result<ScreenerStrategyResponse> {
119 #[derive(Serialize)]
120 struct Query {
121 id: i64,
122 }
123 let raw: serde_json::Value = self
124 .get("/v1/quote/screener/strategy", Query { id })
125 .await?;
126 Ok(ScreenerStrategyResponse { data: raw })
127 }
128
129 pub async fn screener_search(
139 &self,
140 market: impl Into<String>,
141 strategy_id: Option<i64>,
142 page: u32,
143 size: u32,
144 ) -> Result<ScreenerSearchResponse> {
145 #[derive(Debug, Serialize)]
146 struct Body {
147 market: String,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 strategy_id: Option<i64>,
150 page: u32,
151 size: u32,
152 }
153 let raw: serde_json::Value = self
154 .post(
155 "/v1/quote/screener/search",
156 Body {
157 market: market.into(),
158 strategy_id,
159 page,
160 size,
161 },
162 )
163 .await?;
164 Ok(ScreenerSearchResponse { data: raw })
165 }
166
167 pub async fn screener_indicators(&self) -> Result<ScreenerIndicatorsResponse> {
173 #[derive(Serialize)]
174 struct Empty {}
175 let raw: serde_json::Value = self.get("/v1/quote/screener/indicators", Empty {}).await?;
176 Ok(ScreenerIndicatorsResponse { data: raw })
177 }
178}