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