Skip to main content

futucli/cmd/analysis/
security.rs

1//! v1.4.110+ split (from cmd/analysis.rs): security domain.
2//!
3//! pub items: run_user_security, run_user_security_groups.
4
5use 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, // v1.4.110 codex Slice 1 schema 占位
108        },
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}