Skip to main content

tradingview/models/
pine_indicator.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4use ustr::Ustr;
5
6use crate::{
7    Result,
8    chart::study::{IndicatorInput, InputValue},
9    client::misc::get_indicator_metadata,
10    models::{FinancialPeriod, UserCookies},
11};
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum BuiltinIndicators {
15    All,
16    Fundamental,
17    Standard,
18    Candlestick,
19}
20
21impl std::fmt::Display for BuiltinIndicators {
22    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
23        match self {
24            BuiltinIndicators::All => write!(f, "all"),
25            BuiltinIndicators::Fundamental => write!(f, "fundamental"),
26            BuiltinIndicators::Standard => write!(f, "standard"),
27            BuiltinIndicators::Candlestick => write!(f, "candlestick"),
28        }
29    }
30}
31
32#[derive(Debug, Clone, PartialEq, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct PineInfo {
35    pub user_id: i64,
36    pub script_name: Ustr,
37    pub script_source: Ustr,
38    #[serde(rename(deserialize = "scriptIdPart"))]
39    pub script_id: Ustr,
40    pub script_access: Ustr,
41    #[serde(rename(deserialize = "version"))]
42    pub script_version: Ustr,
43    pub extra: PineInfoExtra,
44}
45
46#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
47#[serde(default, rename_all = "camelCase")]
48pub struct PineInfoExtra {
49    pub financial_period: Option<FinancialPeriod>,
50    #[serde(alias = "fund_id")]
51    pub fund_id: Option<Ustr>,
52    pub fundamental_category: Option<Ustr>,
53    pub is_auto: bool,
54    pub is_beta: bool,
55    pub is_built_in: bool,
56    pub is_candle_stick: bool,
57    pub is_fundamental_study: bool,
58    pub is_hidden_study: bool,
59    pub is_mtf_resolution: bool,
60    pub is_new: bool,
61    pub is_pine_editor_new_template: bool,
62    pub is_updated: bool,
63    pub kind: Ustr,
64    pub short_description: Ustr,
65    pub source_inputs_count: i64,
66    pub tags: Vec<Ustr>,
67}
68
69#[derive(Debug, Clone, Deserialize)]
70pub struct TranslateResponse {
71    pub success: bool,
72    pub result: PineMetadata,
73}
74
75#[derive(Debug, Clone, Deserialize)]
76pub struct SearchResponse {
77    pub next: String,
78    pub results: Vec<PineSearchResult>,
79}
80
81#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct PineSearchResult {
84    pub image_url: String,
85    pub script_name: String,
86    pub script_source: String,
87    pub access: i64,
88    pub script_id_part: String,
89    pub version: String,
90    pub extra: PineSearchExtra,
91    pub agree_count: i64,
92    pub author: PineSearchAuthor,
93    pub weight: i64,
94}
95
96#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct PineSearchExtra {
99    pub kind: Option<String>,
100    pub source_inputs_count: Option<i64>,
101    #[serde(rename = "isMTFResolution")]
102    pub is_mtf_resolution: Option<bool>,
103}
104
105#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
106#[serde(rename_all = "camelCase")]
107pub struct PineSearchAuthor {
108    pub id: i64,
109    pub username: Ustr,
110    #[serde(rename = "is_broker")]
111    pub is_broker: bool,
112}
113
114#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
115pub struct PineMetadata {
116    #[serde(rename(deserialize = "IL"))]
117    pub il: Ustr,
118    #[serde(rename(deserialize = "ilTemplate"))]
119    pub il_template: Ustr,
120    #[serde(rename(deserialize = "metaInfo"))]
121    pub data: PineMetadataInfo,
122}
123
124#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
125#[serde(default, rename_all = "camelCase")]
126pub struct PineMetadataInfo {
127    pub id: Ustr,
128    #[serde(rename(deserialize = "scriptIdPart"))]
129    pub script_id: Ustr,
130    pub description: Ustr,
131    pub short_description: Ustr,
132    pub financial_period: Option<FinancialPeriod>,
133    pub grouping_key: Ustr,
134    pub is_fundamental_study: bool,
135    pub is_hidden_study: bool,
136    pub is_tv_script: bool,
137    pub is_tv_script_stub: bool,
138    pub is_price_study: bool,
139    pub inputs: Vec<PineInput>,
140    pub defaults: HashMap<Ustr, Value>,
141    pub palettes: HashMap<Ustr, Value>,
142    pub pine: HashMap<Ustr, Ustr>,
143    pub plots: Vec<Plot>,
144    pub styles: HashMap<Ustr, Value>,
145    pub uses_private_lib: bool,
146    pub warnings: Vec<Value>,
147}
148
149#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default, Copy)]
150#[serde(default, rename_all = "camelCase")]
151pub struct Plot {
152    pub id: Ustr,
153    pub plot_type: Ustr,
154    pub target: Option<Ustr>,
155}
156
157#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
158#[serde(default, rename_all = "camelCase")]
159pub struct PineInput {
160    pub name: Ustr,
161    pub inline: Ustr,
162    pub id: Ustr,
163    pub defval: Value,
164    pub is_hidden: bool,
165    pub is_fake: bool,
166    pub optional: bool,
167    pub options: Vec<Ustr>,
168    pub tooltip: Option<Ustr>,
169    #[serde(rename(deserialize = "type"))]
170    pub input_type: Ustr,
171}
172
173#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, Copy)]
174pub enum ScriptType {
175    #[default]
176    Script,
177    IntervalScript,
178    StrategyScript,
179    VolumeBasicStudies,
180    FixedBasicStudies,
181    FixedVolumeByPrice,
182    SessionVolumeByPrice,
183    SessionRoughVolumeByPrice,
184    SessionDetailedVolumeByPrice,
185    VisibleVolumeByPrice,
186}
187
188impl std::fmt::Display for ScriptType {
189    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
190        match self {
191            ScriptType::Script => write!(f, "Script@tv-scripting-101!"),
192            ScriptType::IntervalScript => write!(f, "Internal@tv-scripting-101!"),
193            ScriptType::StrategyScript => write!(f, "StrategyScript@tv-scripting-101!"),
194            ScriptType::VolumeBasicStudies => write!(f, "Volume@tv-basicstudies-144"),
195            ScriptType::FixedBasicStudies => write!(f, "VbPFixed@tv-basicstudies-139!"),
196            ScriptType::FixedVolumeByPrice => write!(f, "VbPFixed@tv-volumebyprice-53!"),
197            ScriptType::SessionVolumeByPrice => write!(f, "VbPSessions@tv-volumebyprice-53"),
198            ScriptType::SessionRoughVolumeByPrice => {
199                write!(f, "VbPSessionsRough@tv-volumebyprice-53!")
200            }
201            ScriptType::SessionDetailedVolumeByPrice => {
202                write!(f, "VbPSessionsDetailed@tv-volumebyprice-53!")
203            }
204            ScriptType::VisibleVolumeByPrice => write!(f, "VbPVisible@tv-volumebyprice-53"),
205            //Sessions@tv-basicstudies-187!
206            //Dividends@tv-basicstudies-251
207            //Splits@tv-basicstudies-251
208            //Earnings@tv-basicstudies-251
209            //Volume@tv-basicstudies-251 , Opts {"length":20,"col_prev_close":false}
210        }
211    }
212}
213
214impl From<String> for ScriptType {
215    fn from(_value: String) -> Self {
216        todo!()
217    }
218}
219
220#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
221pub struct PineIndicator {
222    pub script_id: Ustr,
223    pub script_version: Ustr,
224    pub script_type: ScriptType,
225    pub metadata: PineMetadata,
226}
227
228pub struct PineIndicatorBuilder {
229    user: Option<UserCookies>,
230}
231
232impl PineIndicatorBuilder {
233    pub fn user(&mut self, user: UserCookies) -> &mut Self {
234        self.user = Some(user);
235        self
236    }
237
238    pub async fn fetch(
239        &mut self,
240        script_id: &str,
241        script_version: &str,
242        script_type: ScriptType,
243    ) -> Result<PineIndicator> {
244        let metadata = match &self.user {
245            Some(user) => get_indicator_metadata(Some(user), script_id, script_version).await?,
246            None => get_indicator_metadata(None, script_id, script_version).await?,
247        };
248        Ok(PineIndicator {
249            script_id: Ustr::from(script_id),
250            script_version: Ustr::from(script_version),
251            script_type,
252            metadata,
253        })
254    }
255}
256
257impl PineIndicator {
258    pub fn build() -> PineIndicatorBuilder {
259        PineIndicatorBuilder { user: None }
260    }
261
262    pub fn to_study_inputs(&self) -> Result<Value> {
263        let mut inputs: HashMap<Ustr, IndicatorInput> = HashMap::new();
264        inputs.insert(
265            Ustr::from("text"),
266            IndicatorInput::String(Ustr::from(&self.metadata.il_template)),
267        );
268        inputs.insert(
269            Ustr::from("pineId"),
270            IndicatorInput::String(Ustr::from(&self.script_id)),
271        );
272        inputs.insert(
273            Ustr::from("pineVersion"),
274            IndicatorInput::String(Ustr::from(&self.script_version)),
275        );
276        self.metadata.data.inputs.iter().for_each(|input| {
277            if input.id == "text" || input.id == "pineId" || input.id == "pineVersion" {
278                return;
279            }
280            inputs.insert(
281                Ustr::from(&input.id),
282                IndicatorInput::IndicatorInput(InputValue {
283                    v: input.defval.clone(),
284                    f: Value::from(input.is_fake),
285                    t: Value::from(input.input_type.to_string()),
286                }),
287            );
288        });
289
290        let json_value = serde_json::to_value(inputs)?;
291        Ok(json_value)
292    }
293}