futucli/cmd/analysis/
screen.rs1use anyhow::{Result, anyhow, bail};
4use prost::Message;
5use serde::Serialize;
6use tabled::Tabled;
7
8use crate::common::connect_gateway;
9use crate::output::OutputFormat;
10
11#[derive(Tabled)]
12struct StockScreenRow {
13 #[tabled(rename = "Stock ID")]
14 stock_id: String,
15 #[tabled(rename = "Results")]
16 result_count: usize,
17}
18
19#[derive(Serialize)]
20struct StockScreenJson {
21 data_list: Vec<futu_proto::qot_stock_screen::StockScreenItem>,
22 last_page: Option<i32>,
23 all_count: Option<i32>,
24}
25
26#[derive(Tabled)]
27struct OptionScreenRow {
28 #[tabled(rename = "Security")]
29 security: String,
30 #[tabled(rename = "Name")]
31 name: String,
32 #[tabled(rename = "Price")]
33 price: String,
34}
35
36#[derive(Serialize)]
37struct OptionScreenJson {
38 data_list: Vec<futu_proto::qot_option_screen::OptionScreenItem>,
39 last_page: bool,
40 all_count: i32,
41}
42
43#[derive(Tabled)]
44struct WarrantScreenRow {
45 #[tabled(rename = "Stock ID")]
46 stock_id: String,
47 #[tabled(rename = "Owner")]
48 stock_owner: String,
49 #[tabled(rename = "Price")]
50 current_price: String,
51}
52
53#[derive(Serialize)]
54struct WarrantScreenJson {
55 warrants: Vec<futu_proto::qot_warrant_screen::WarrantItem>,
56 last_page: Option<bool>,
57 all_count: Option<i32>,
58}
59
60fn parse_stock_screen_c2s(json: &str) -> Result<futu_proto::qot_stock_screen::C2s> {
61 let value: serde_json::Value =
62 serde_json::from_str(json).map_err(|e| anyhow!("stock-screen c2s json: {e}"))?;
63 serde_json::from_value(value).map_err(|e| anyhow!("stock-screen c2s json: {e}"))
64}
65
66fn parse_option_screen_c2s(json: &str) -> Result<futu_proto::qot_option_screen::C2s> {
67 let value: serde_json::Value =
68 serde_json::from_str(json).map_err(|e| anyhow!("option-screen c2s json: {e}"))?;
69 serde_json::from_value(value).map_err(|e| anyhow!("option-screen c2s json: {e}"))
70}
71
72fn parse_warrant_screen_c2s(json: &str) -> Result<futu_proto::qot_warrant_screen::C2s> {
73 let value: serde_json::Value =
74 serde_json::from_str(json).map_err(|e| anyhow!("warrant-screen c2s json: {e}"))?;
75 serde_json::from_value(value).map_err(|e| anyhow!("warrant-screen c2s json: {e}"))
76}
77
78pub async fn run_stock_screen(gateway: &str, c2s_json: &str, format: OutputFormat) -> Result<()> {
79 let c2s = parse_stock_screen_c2s(c2s_json)?;
80 let (client, _rx) = connect_gateway(gateway, "futucli-stock-screen").await?;
81 let req = futu_proto::qot_stock_screen::Request { c2s };
82 let frame = client
83 .request(futu_core::proto_id::QOT_STOCK_SCREEN, req.encode_to_vec())
84 .await?;
85 let resp = futu_proto::qot_stock_screen::Response::decode(frame.body.as_ref())
86 .map_err(|e| anyhow!("decode stock_screen: {e}"))?;
87 if resp.ret_type != 0 {
88 bail!(
89 "stock_screen ret_type={} msg={:?}",
90 resp.ret_type,
91 resp.ret_msg
92 );
93 }
94 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
95 let rows: Vec<StockScreenRow> = s2c
96 .data_list
97 .iter()
98 .map(|item| StockScreenRow {
99 stock_id: item.stock_id.map(|id| id.to_string()).unwrap_or_default(),
100 result_count: item.results.len(),
101 })
102 .collect();
103 let json = StockScreenJson {
104 data_list: s2c.data_list,
105 last_page: s2c.last_page,
106 all_count: s2c.all_count,
107 };
108 format.print_rows(&rows, &[json])?;
109 Ok(())
110}
111
112pub async fn run_option_screen(gateway: &str, c2s_json: &str, format: OutputFormat) -> Result<()> {
113 let c2s = parse_option_screen_c2s(c2s_json)?;
114 let (client, _rx) = connect_gateway(gateway, "futucli-option-screen").await?;
115 let req = futu_proto::qot_option_screen::Request { c2s };
116 let frame = client
117 .request(futu_core::proto_id::QOT_OPTION_SCREEN, req.encode_to_vec())
118 .await?;
119 let resp = futu_proto::qot_option_screen::Response::decode(frame.body.as_ref())
120 .map_err(|e| anyhow!("decode option_screen: {e}"))?;
121 if resp.ret_type != 0 {
122 bail!(
123 "option_screen ret_type={} msg={:?}",
124 resp.ret_type,
125 resp.ret_msg
126 );
127 }
128 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
129 let rows: Vec<OptionScreenRow> = s2c
130 .data_list
131 .iter()
132 .map(|item| {
133 let security = item
134 .security
135 .as_ref()
136 .map(|sec| format!("{}.{}", sec.market, sec.code))
137 .unwrap_or_default();
138 OptionScreenRow {
139 security,
140 name: item.option_name.clone().unwrap_or_default(),
141 price: item.price.map(|v| v.to_string()).unwrap_or_default(),
142 }
143 })
144 .collect();
145 let json = OptionScreenJson {
146 data_list: s2c.data_list,
147 last_page: s2c.last_page,
148 all_count: s2c.all_count,
149 };
150 format.print_rows(&rows, &[json])?;
151 Ok(())
152}
153
154pub async fn run_warrant_screen(gateway: &str, c2s_json: &str, format: OutputFormat) -> Result<()> {
155 let c2s = parse_warrant_screen_c2s(c2s_json)?;
156 let (client, _rx) = connect_gateway(gateway, "futucli-warrant-screen").await?;
157 let req = futu_proto::qot_warrant_screen::Request { c2s };
158 let frame = client
159 .request(futu_core::proto_id::QOT_WARRANT_SCREEN, req.encode_to_vec())
160 .await?;
161 let resp = futu_proto::qot_warrant_screen::Response::decode(frame.body.as_ref())
162 .map_err(|e| anyhow!("decode warrant_screen: {e}"))?;
163 if resp.ret_type != 0 {
164 bail!(
165 "warrant_screen ret_type={} msg={:?}",
166 resp.ret_type,
167 resp.ret_msg
168 );
169 }
170 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
171 let rows: Vec<WarrantScreenRow> = s2c
172 .warrants
173 .iter()
174 .map(|item| WarrantScreenRow {
175 stock_id: item.stock_id.map(|id| id.to_string()).unwrap_or_default(),
176 stock_owner: item
177 .stock_owner
178 .map(|id| id.to_string())
179 .unwrap_or_default(),
180 current_price: item
181 .current_price
182 .map(|price| price.to_string())
183 .unwrap_or_default(),
184 })
185 .collect();
186 let json = WarrantScreenJson {
187 warrants: s2c.warrants,
188 last_page: s2c.last_page,
189 all_count: s2c.all_count,
190 };
191 format.print_rows(&rows, &[json])?;
192 Ok(())
193}