1use std::collections::HashMap;
2
3use anyhow::{Context, Result};
4use clap::{Args, Parser, Subcommand};
5use easytradeview::{Field, FieldWithInterval, Interval, Screener, SymbolValues, TradingView};
6use serde_json::{json, Value};
7
8#[derive(Parser)]
9struct Opts {
10 #[command(subcommand)]
11 subcmd: SubCommand,
12}
13
14#[derive(Subcommand)]
15enum SubCommand {
16 Search(SerachArgs),
18 Scan(ScanArgs),
20 Get(GetArgs),
22 ListScreeners,
24 ListIntervals,
26}
27
28#[derive(Debug, Args)]
29struct SerachArgs {
30 #[arg(long, default_value = "crypto")]
31 screener: String,
32
33 #[arg(long)]
34 exchanges: Vec<String>,
35
36 #[arg(long)]
37 types: Vec<String>,
38
39 #[arg(long, default_value = "")]
41 name: String,
42
43 #[arg(long, value_parser, num_args = 1.., value_delimiter = ',')]
44 fields: Vec<String>,
45}
46
47#[derive(Debug, Args)]
48struct ScanArgs {
49 #[arg(long, default_value = "crypto")]
50 screener: String,
51
52 #[arg(long, default_value = "OKX")]
53 exchange: String,
54
55 #[arg(short, long, default_value = "1d")]
56 default_interval: String,
57
58 #[arg(long, default_value = "BTCUSDT", value_parser, num_args = 1.., value_delimiter = ',')]
59 symbols: Vec<String>,
60
61 #[arg(long, default_value = "1d", value_parser, num_args = 1.., value_delimiter = ',')]
62 fields: Vec<String>,
63}
64
65#[derive(Debug, Args)]
66struct GetArgs {
67 #[arg(long, default_value = "crypto")]
68 screener: String,
69
70 #[arg(long, default_value = "OKX")]
71 exchange: String,
72
73 #[arg(short, long, default_value = "1d")]
74 interval: String,
75
76 #[arg(long, default_value = "BTCUSDT")]
77 symbol: String,
78
79 #[arg(long, default_value = "1d", value_parser, num_args = 1.., value_delimiter = ',')]
80 fields: Vec<String>,
81
82 #[arg(long, default_value_t = false)]
83 json: bool,
84}
85
86#[tokio::main]
87async fn main() -> Result<()> {
88 let opts = Opts::parse();
89 match opts.subcmd {
90 SubCommand::Search(args) => {
91 let screener = args.screener;
93 let exchanges = args.exchanges;
94 let types = args.types;
95 let name_filter = args.name;
96 let extra_fields: Vec<FieldWithInterval> = args
98 .fields
99 .into_iter()
100 .map(|x| FieldWithInterval::parse_undefined(&x))
101 .collect();
102
103 let tv = TradingView::new(&screener, "");
105 let values = tv
106 .search_symbols(&exchanges, &types, name_filter, &extra_fields)
107 .await
108 .context("search symbols error")?;
109
110 let fields = [
112 Field::Exchange,
113 Field::Name,
114 Field::Type,
115 Field::Description,
116 ]
117 .into_iter()
118 .map(|x| x.with_interval(&Interval::default()))
119 .chain(extra_fields.into_iter())
120 .collect::<Vec<_>>();
121
122 let mut table: Vec<Vec<String>> = vec![];
124 for vals in values {
125 let mut row = vec![];
126 for field in fields.iter() {
127 let val = vals
128 .values()
129 .get(&field)
130 .map_or("".into(), |x| x.to_string());
131 row.push(val);
132 }
133 table.push(row);
134 }
135
136 println!(
138 "{}",
139 fields
140 .iter()
141 .map(|x| x.to_string())
142 .collect::<Vec<_>>()
143 .join(",")
144 );
145 for vals in table {
147 println!("{}", vals.join(","));
148 }
149 }
150 SubCommand::Scan(args) => {
151 let screener = args.screener;
153 let exchange = args.exchange;
154 let interval = Interval::parse_undefined(&args.default_interval);
155 let symbols = args.symbols;
156 let fields = args
158 .fields
159 .iter()
160 .map(|x| FieldWithInterval::parse_undefined_with_default_interval(x, &interval))
161 .collect::<Vec<_>>();
162
163 let tv = TradingView::new(&screener, exchange);
165 let symbols = tv
166 .scan_symbols(&symbols, &fields)
167 .await
168 .context("scan symbols error")?;
169
170 let mut table: Vec<Vec<String>> = vec![];
172 for vals in symbols {
173 let mut row = vec![format!("\"{}\"", vals.symbol())];
174 for field in fields.iter() {
175 let val = vals
176 .values()
177 .get(&field)
178 .map_or("".into(), |x| x.to_string());
179 row.push(val);
180 }
181 table.push(row);
182 }
183
184 println!(
186 "symbol,{}",
187 fields
188 .iter()
189 .map(|x| x.to_string())
190 .collect::<Vec<_>>()
191 .join(",")
192 );
193 for vals in table {
195 println!("{}", vals.join(","));
196 }
197 }
198 SubCommand::Get(args) => {
199 let screener = args.screener;
201 let exchange = args.exchange;
202 let interval = Interval::parse_undefined(&args.interval);
203 let symbol = args.symbol;
204 let fields = args
206 .fields
207 .iter()
208 .map(|x| FieldWithInterval::parse_undefined_with_default_interval(x, &interval))
209 .collect::<Vec<_>>();
210
211 let tv = TradingView::new(&screener, exchange);
213 let symbol = tv
214 .get_symbol_fields_with_interval(&symbol, &fields)
215 .await
216 .context("Failed to retrieve symbol fields")?;
217
218 let mut values: Vec<(String, Value)> = Vec::new();
220 values.extend(vec![
221 ("screener".to_string(), json!(screener.to_string())),
222 ("symbol".to_string(), json!(symbol.symbol().to_string())),
223 ("interval".to_string(), json!(interval.to_string())),
224 ]);
225 for field in &fields {
226 if let Some(val) = symbol.values().get(field) {
227 values.push((field.to_string(), val.clone()));
228 }
229 }
230
231 if args.json {
233 println!(
234 "{}",
235 serde_json::to_string_pretty(&values.into_iter().collect::<HashMap<_, _>>())?
236 );
237 } else {
238 for (k, v) in values {
239 println!("{:>13} : {}", k, v.to_string());
240 }
241 }
242 }
243 SubCommand::ListScreeners => {
244 for screener in Screener::all_screeners() {
245 println!("{:?}", screener);
246 }
247 }
248 SubCommand::ListIntervals => {
249 for interval in Interval::all_intervals() {
250 println!("{}", interval);
251 }
252 }
253 };
254 Ok(())
255}