Skip to main content

futu_mcp/handlers/core/
sub_query.rs

1//! mcp/handlers/core/sub_query — SubInfoItem/Out + UsedQuotaOut + query_subscription + get_used_quota
2//! (v1.4.110 CC Batch N: 拆自 core.rs L707-807)
3
4use std::sync::Arc;
5
6use anyhow::{Result, anyhow, bail};
7use futu_net::client::FutuClient;
8use prost::Message;
9use serde::Serialize;
10
11#[derive(Serialize)]
12struct SubInfoItemOut {
13    sub_type: i32,
14    symbols: Vec<String>,
15}
16
17#[derive(Serialize)]
18struct SubInfoOut {
19    total_used_quota: i32,
20    remain_quota: i32,
21    sub_list: Vec<SubInfoItemOut>,
22}
23
24#[derive(Serialize)]
25struct UsedQuotaOut {
26    used_sub_quota: i32,
27    used_k_line_quota: i32,
28}
29
30/// 查询当前连接的订阅信息(订阅过哪些类型、各用了多少额度)。
31pub async fn query_subscription(client: &Arc<FutuClient>, is_req_all_conn: bool) -> Result<String> {
32    let req = futu_proto::qot_get_sub_info::Request {
33        c2s: futu_proto::qot_get_sub_info::C2s {
34            is_req_all_conn: Some(is_req_all_conn),
35            header: None,
36        },
37    };
38    let body = req.encode_to_vec();
39    let frame = client
40        .request(futu_core::proto_id::QOT_GET_SUB_INFO, body)
41        .await?;
42    let resp = futu_proto::qot_get_sub_info::Response::decode(frame.body.as_ref())
43        .map_err(|e| anyhow!("decode sub_info: {e}"))?;
44    if resp.ret_type != 0 {
45        bail!("sub_info ret_type={} msg={:?}", resp.ret_type, resp.ret_msg);
46    }
47    let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
48    // 扁平化所有 conn 的 sub_info_list(大多数用户 is_req_all_conn=false,
49    // 只有自己这条连接,即单层)
50    let mut sub_list = Vec::new();
51    for conn in &s.conn_sub_info_list {
52        for si in &conn.sub_info_list {
53            sub_list.push(SubInfoItemOut {
54                sub_type: si.sub_type,
55                symbols: si
56                    .security_list
57                    .iter()
58                    .map(|sec| format!("{}.{}", sec.market, sec.code))
59                    .collect(),
60            });
61        }
62    }
63    let out = SubInfoOut {
64        total_used_quota: s.total_used_quota,
65        remain_quota: s.remain_quota,
66        sub_list,
67    };
68    Ok(serde_json::to_string_pretty(&out)?)
69}
70
71/// 查询当前 daemon 已用订阅额度与历史 K 线额度。
72pub async fn get_used_quota(client: &Arc<FutuClient>) -> Result<String> {
73    let req = futu_proto::used_quota::Request {
74        c2s: futu_proto::used_quota::C2s {},
75    };
76    let body = req.encode_to_vec();
77    let frame = client
78        .request(futu_core::proto_id::GET_USED_QUOTA, body)
79        .await?;
80    let resp = futu_proto::used_quota::Response::decode(frame.body.as_ref())
81        .map_err(|e| anyhow!("decode used_quota: {e}"))?;
82    if resp.ret_type != 0 {
83        bail!(
84            "used_quota ret_type={} msg={:?}",
85            resp.ret_type,
86            resp.ret_msg
87        );
88    }
89    let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
90    let out = UsedQuotaOut {
91        used_sub_quota: s
92            .used_sub_quota
93            .ok_or_else(|| anyhow!("missing used_sub_quota"))?,
94        used_k_line_quota: s
95            .used_k_line_quota
96            .ok_or_else(|| anyhow!("missing used_k_line_quota"))?,
97    };
98    Ok(serde_json::to_string_pretty(&out)?)
99}