Skip to main content

futu_mcp/handlers/trade/
account_flag.rs

1use std::sync::Arc;
2
3use anyhow::{Result, bail};
4use futu_backend::proto_internal::account_flag;
5use futu_net::client::FutuClient;
6use prost::Message as _;
7use serde::Serialize;
8
9use super::parse_trd_env_int;
10
11// ============================================================================
12// v1.4.95 U2-A Tier M (mobile-driven extension): account compliance flags
13//
14// 来源: ftcnnproto/.../account_flag.proto + NN cmd 5281.
15// 用户高级交易准入 (期权 / 衍生品 / OTC / CFD 等) 强制要求.
16// LLM agent 用此 tool 检查用户是否完成 KYC / 风披 / opt-in.
17// ============================================================================
18
19#[derive(Serialize)]
20struct AccountFlagOut {
21    item_present: bool,
22    flag_value_present: bool,
23    uid: u64,
24    flag_id: i32,
25    /// 标志取值 (通常 0=未确认 / 1=已确认; 部分 flag 用 version 号)
26    flag_value: i32,
27    /// 最后修改时间 (epoch 秒)
28    updated_time: u64,
29}
30
31fn account_flag_out_from_proto(
32    inner_rsp: account_flag::GetFlagRsp,
33    requested_flag_id: u32,
34) -> AccountFlagOut {
35    let item_present = inner_rsp.item.is_some();
36    let item = inner_rsp.item.unwrap_or_default();
37    let flag_value_present = item.flag_value.is_some();
38    AccountFlagOut {
39        item_present,
40        flag_value_present,
41        uid: item.uid.unwrap_or(0),
42        flag_id: item.flag_id.unwrap_or(requested_flag_id as i32),
43        flag_value: item.flag_value.unwrap_or(0),
44        updated_time: item.updated_time.unwrap_or(0),
45    }
46}
47
48/// v1.4.95 U2-A: MCP tool `futu_get_account_flag` — 查询账户合规标志
49///
50/// **常用 flag_id** (mobile proto 头部完整 36+ 项, 列出常用):
51/// - 5  = US 期权风险确认
52/// - 8  = 期权交易测评完成
53/// - 10 = 基金 KYC 评级 (R1~R5 → 1~5)
54/// - 11 = HK 期权风险确认
55/// - 16 = PDT 风险披露确认
56/// - 22 = 衍生品交易风批标记 (新, 合并)
57/// - 23 = 美股 OTC 交易风险测评和风险披露
58/// - 24 = 港股期权测评
59/// - 25 = 算法交易风险披露
60/// - 34 = 人脸识别风批 (HK/US/SG)
61/// - 46 = OpenAPI 免责标记位 (AU)
62pub async fn get_account_flag(
63    client: &Arc<FutuClient>,
64    env: &str,
65    acc_id: u64,
66    flag_id: u32,
67) -> Result<String> {
68    if acc_id == 0 {
69        bail!("acc_id 必填 (call futu_list_accounts to discover)");
70    }
71    if flag_id == 0 {
72        bail!(
73            "flag_id 必填 (常用值: 5=US 期权确认, 22=衍生品风批, 10=基金 KYC, 16=PDT, \
74             23=美股 OTC. 详见 proto 头部完整列表)"
75        );
76    }
77    // v1.4.102 codex 42 F3: 严格 env, typo reject.
78    let trd_env_int: i32 = parse_trd_env_int(env)?;
79
80    let req = account_flag::DaemonGetAccountFlagReq {
81        c2s: account_flag::daemon_get_account_flag_req::C2s {
82            header: account_flag::DaemonGetAccountFlagHeader {
83                acc_id,
84                trd_env: Some(trd_env_int),
85                flag_id,
86            },
87        },
88    };
89
90    let body = req.encode_to_vec();
91    let frame = client
92        .request(futu_core::proto_id::TRD_GET_ACCOUNT_FLAG, body)
93        .await?;
94    let resp =
95        <account_flag::DaemonGetAccountFlagRsp as prost::Message>::decode(frame.body.as_ref())
96            .map_err(|e| anyhow::anyhow!("decode DaemonGetAccountFlagRsp: {e}"))?;
97    if resp.ret_type != 0 {
98        bail!(
99            "GetAccountFlag ret_type={} msg={:?}",
100            resp.ret_type,
101            resp.ret_msg
102        );
103    }
104
105    let inner_rsp = resp
106        .s2c
107        .and_then(|s| s.inner)
108        .ok_or_else(|| anyhow::anyhow!("empty s2c.inner in GetAccountFlagRsp"))?;
109
110    let out = account_flag_out_from_proto(inner_rsp, flag_id);
111
112    Ok(serde_json::to_string_pretty(&out)?)
113}
114
115#[cfg(test)]
116mod tests;