futucli/cmd/analysis/
security.rs1use anyhow::{Result, anyhow, bail};
6use prost::Message;
7use serde::Serialize;
8use tabled::Tabled;
9
10use crate::common::connect_gateway;
11use crate::output::OutputFormat;
12
13#[derive(Tabled)]
14struct UserSecurityRow {
15 #[tabled(rename = "Stock ID")]
16 stock_id: i64,
17 #[tabled(rename = "Code")]
18 code: String,
19 #[tabled(rename = "Name")]
20 name: String,
21 #[tabled(rename = "Lot")]
22 lot: i32,
23 #[tabled(rename = "Type")]
24 sec_type: i32,
25}
26
27#[derive(Serialize)]
28struct UserSecurityJson {
29 stock_id: i64,
30 code: String,
31 name: String,
32 lot_size: i32,
33 sec_type: i32,
34}
35
36pub async fn run_user_security(
37 gateway: &str,
38 group_name: &str,
39 format: OutputFormat,
40) -> Result<()> {
41 let (client, _rx) = connect_gateway(gateway, "futucli-user-security").await?;
42 let req = futu_proto::qot_get_user_security::Request {
43 c2s: futu_proto::qot_get_user_security::C2s {
44 group_name: group_name.to_string(),
45 header: None,
46 },
47 };
48 let body = req.encode_to_vec();
49 let frame = client
50 .request(futu_core::proto_id::QOT_GET_USER_SECURITY, body)
51 .await?;
52 let resp = futu_proto::qot_get_user_security::Response::decode(frame.body.as_ref())
53 .map_err(|e| anyhow!("decode user_security: {e}"))?;
54 if resp.ret_type != 0 {
55 bail!(
56 "user_security ret_type={} msg={:?}",
57 resp.ret_type,
58 resp.ret_msg
59 );
60 }
61 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
62 let mut rows = Vec::new();
63 let mut jsons = Vec::new();
64 for s in &s2c.static_info_list {
65 rows.push(UserSecurityRow {
66 stock_id: s.basic.id,
67 code: s.basic.security.code.clone(),
68 name: s.basic.name.clone(),
69 lot: s.basic.lot_size,
70 sec_type: s.basic.sec_type,
71 });
72 jsons.push(UserSecurityJson {
73 stock_id: s.basic.id,
74 code: s.basic.security.code.clone(),
75 name: s.basic.name.clone(),
76 lot_size: s.basic.lot_size,
77 sec_type: s.basic.sec_type,
78 });
79 }
80 format.print_rows(&rows, &jsons)?;
81 Ok(())
82}
83
84#[derive(Tabled)]
85struct GroupRow {
86 #[tabled(rename = "Name")]
87 name: String,
88 #[tabled(rename = "Type")]
89 gtype: String,
90}
91
92#[derive(Serialize)]
93struct GroupJson {
94 group_name: String,
95 group_type: i32,
96}
97
98pub async fn run_user_security_groups(
99 gateway: &str,
100 group_type: i32,
101 format: OutputFormat,
102) -> Result<()> {
103 let (client, _rx) = connect_gateway(gateway, "futucli-user-security-groups").await?;
104 let req = futu_proto::qot_get_user_security_group::Request {
105 c2s: futu_proto::qot_get_user_security_group::C2s {
106 group_type,
107 header: None, },
109 };
110 let body = req.encode_to_vec();
111 let frame = client
112 .request(futu_core::proto_id::QOT_GET_USER_SECURITY_GROUP, body)
113 .await?;
114 let resp = futu_proto::qot_get_user_security_group::Response::decode(frame.body.as_ref())
115 .map_err(|e| anyhow!("decode user_security_group: {e}"))?;
116 if resp.ret_type != 0 {
117 bail!(
118 "user_security_group ret_type={} msg={:?}",
119 resp.ret_type,
120 resp.ret_msg
121 );
122 }
123 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
124 let mut rows = Vec::new();
125 let mut jsons = Vec::new();
126 for g in &s2c.group_list {
127 let label = match g.group_type {
128 1 => "Custom",
129 2 => "System",
130 3 => "All",
131 _ => "?",
132 };
133 rows.push(GroupRow {
134 name: g.group_name.clone(),
135 gtype: format!("{} ({})", label, g.group_type),
136 });
137 jsons.push(GroupJson {
138 group_name: g.group_name.clone(),
139 group_type: g.group_type,
140 });
141 }
142 format.print_rows(&rows, &jsons)?;
143 Ok(())
144}