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 }
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}