Skip to main content

futu_qot/
quote_rights.rs

1//! 用户行情权限 profile。
2//!
3//! 这个 module 是 CLI / REST / MCP 的共享投影层:`GetUserInfo` 保持 SDK
4//! 兼容语义,GUI 风格权限面板则从 daemon 内部权限 cache snapshot 生成。
5
6use serde::{Deserialize, Serialize};
7
8pub const SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE: &str = "sys.get_quote_rights_profile";
9pub const TEST_CMD_GET_QUOTE_RIGHTS_PROFILE: &str = "get_quote_rights_profile";
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct QuoteRightsProfile {
13    pub nick_name: Option<String>,
14    pub user_id: Option<i64>,
15    pub user_attribution: Option<i32>,
16    pub user_attribution_region: Option<String>,
17    pub ret_msg: Option<String>,
18    pub quota: QuoteRightsQuota,
19    pub items: Vec<QuoteRightItem>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct QuoteRightsQuota {
24    pub subscribe_total: Option<i32>,
25    pub history_kl_total: Option<i32>,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct QuoteRightItem {
30    pub market: String,
31    pub category: String,
32    pub raw: Option<i32>,
33    pub label: String,
34}
35
36#[derive(Debug, Clone, Default, PartialEq, Eq)]
37pub struct QuoteRightsSnapshot {
38    pub nick_name: Option<String>,
39    pub user_id: Option<i64>,
40    pub user_attribution: Option<i32>,
41    pub ret_msg: Option<String>,
42    pub sub_quota: Option<i32>,
43    pub history_kl_quota: Option<i32>,
44    pub hk_qot_right: Option<i32>,
45    pub hk_option_qot_right: Option<i32>,
46    pub hk_future_qot_right: Option<i32>,
47    pub us_qot_right: Option<i32>,
48    pub us_index_qot_right: Option<i32>,
49    pub us_option_qot_right: Option<i32>,
50    pub us_cme_future_qot_right: Option<i32>,
51    pub us_cbot_future_qot_right: Option<i32>,
52    pub us_nymex_future_qot_right: Option<i32>,
53    pub us_comex_future_qot_right: Option<i32>,
54    pub us_cboe_future_qot_right: Option<i32>,
55    pub us_otc_qot_right: Option<i32>,
56    pub sh_qot_right: Option<i32>,
57    pub sz_qot_right: Option<i32>,
58    pub sg_future_qot_right: Option<i32>,
59    pub jp_future_qot_right: Option<i32>,
60    pub cc_qot_right: Option<i32>,
61}
62
63pub fn profile_from_get_user_info(
64    resp: &futu_proto::get_user_info::Response,
65) -> Result<QuoteRightsProfile, String> {
66    if resp.ret_type != 0 {
67        return Err(format!(
68            "get_user_info ret_type={} msg={}",
69            resp.ret_type,
70            resp.ret_msg.as_deref().unwrap_or("")
71        ));
72    }
73    let s = resp.s2c.as_ref().ok_or_else(|| {
74        format!(
75            "get_user_info ret_type=0 but missing s2c msg={}",
76            resp.ret_msg.as_deref().unwrap_or("")
77        )
78    })?;
79
80    Ok(profile_from_snapshot(QuoteRightsSnapshot {
81        nick_name: s.nick_name.clone(),
82        user_id: s.user_id,
83        user_attribution: s.user_attribution,
84        ret_msg: resp.ret_msg.clone(),
85        sub_quota: s.sub_quota,
86        history_kl_quota: s.history_kl_quota,
87        hk_qot_right: s.hk_qot_right,
88        hk_option_qot_right: s.hk_option_qot_right,
89        hk_future_qot_right: s.hk_future_qot_right,
90        us_qot_right: s.us_qot_right,
91        us_index_qot_right: s.us_index_qot_right,
92        us_option_qot_right: s.us_option_qot_right,
93        us_cme_future_qot_right: s.us_cme_future_qot_right,
94        us_cbot_future_qot_right: s.us_cbot_future_qot_right,
95        us_nymex_future_qot_right: s.us_nymex_future_qot_right,
96        us_comex_future_qot_right: s.us_comex_future_qot_right,
97        us_cboe_future_qot_right: s.us_cboe_future_qot_right,
98        us_otc_qot_right: s.us_otc_qot_right,
99        sh_qot_right: s.sh_qot_right,
100        sz_qot_right: s.sz_qot_right,
101        sg_future_qot_right: s.sg_future_qot_right,
102        jp_future_qot_right: s.jp_future_qot_right,
103        cc_qot_right: s.cc_qot_right,
104    }))
105}
106
107pub fn profile_from_snapshot(s: QuoteRightsSnapshot) -> QuoteRightsProfile {
108    let mut items = Vec::with_capacity(18);
109    push_item(
110        &mut items,
111        "香港市场",
112        "股票",
113        s.hk_qot_right,
114        label_standard,
115    );
116    push_item(
117        &mut items,
118        "香港市场",
119        "期权",
120        s.hk_option_qot_right,
121        label_standard,
122    );
123    push_item(
124        &mut items,
125        "香港市场",
126        "期货",
127        s.hk_future_qot_right,
128        label_standard,
129    );
130    push_item(
131        &mut items,
132        "美国市场",
133        "股票",
134        s.us_qot_right,
135        label_us_stock,
136    );
137    push_item(
138        &mut items,
139        "美国市场",
140        "指数",
141        s.us_index_qot_right,
142        label_standard,
143    );
144    push_item(
145        &mut items,
146        "美国市场",
147        "期权",
148        s.us_option_qot_right,
149        label_standard,
150    );
151    push_item(
152        &mut items,
153        "美国市场",
154        "CME",
155        s.us_cme_future_qot_right,
156        label_standard,
157    );
158    push_item(
159        &mut items,
160        "美国市场",
161        "CBOT",
162        s.us_cbot_future_qot_right,
163        label_standard,
164    );
165    push_item(
166        &mut items,
167        "美国市场",
168        "NYMEX",
169        s.us_nymex_future_qot_right,
170        label_standard,
171    );
172    push_item(
173        &mut items,
174        "美国市场",
175        "COMEX",
176        s.us_comex_future_qot_right,
177        label_standard,
178    );
179    push_item(
180        &mut items,
181        "美国市场",
182        "CBOE",
183        s.us_cboe_future_qot_right,
184        label_standard,
185    );
186    push_item(
187        &mut items,
188        "美国市场",
189        "OTC",
190        s.us_otc_qot_right,
191        label_standard,
192    );
193    push_item(
194        &mut items,
195        "A股市场",
196        "上证",
197        s.sh_qot_right,
198        label_standard,
199    );
200    push_item(
201        &mut items,
202        "A股市场",
203        "深证",
204        s.sz_qot_right,
205        label_standard,
206    );
207    push_item(
208        &mut items,
209        "新加坡市场",
210        "期货",
211        s.sg_future_qot_right,
212        label_standard,
213    );
214    push_item(
215        &mut items,
216        "日本市场",
217        "期货",
218        s.jp_future_qot_right,
219        label_standard,
220    );
221    push_item(
222        &mut items,
223        "加密货币市场",
224        "加密货币",
225        s.cc_qot_right,
226        label_standard,
227    );
228
229    QuoteRightsProfile {
230        nick_name: s.nick_name,
231        user_id: s.user_id,
232        user_attribution: s.user_attribution,
233        user_attribution_region: user_attribution_region(s.user_attribution)
234            .map(ToString::to_string),
235        ret_msg: s.ret_msg,
236        quota: QuoteRightsQuota {
237            subscribe_total: s.sub_quota,
238            history_kl_total: s.history_kl_quota,
239        },
240        items,
241    }
242}
243
244fn push_item(
245    items: &mut Vec<QuoteRightItem>,
246    market: &str,
247    category: &str,
248    raw: Option<i32>,
249    label_fn: fn(Option<i32>) -> String,
250) {
251    items.push(QuoteRightItem {
252        market: market.to_string(),
253        category: category.to_string(),
254        raw,
255        label: label_fn(raw),
256    });
257}
258
259fn label_standard(raw: Option<i32>) -> String {
260    match raw {
261        None | Some(0) => "未知".to_string(),
262        Some(1) => "BMP".to_string(),
263        Some(2) => "LV1".to_string(),
264        Some(3) => "LV2".to_string(),
265        Some(4) => "SF".to_string(),
266        Some(5) => "无权限".to_string(),
267        Some(v) => format!("未知({v})"),
268    }
269}
270
271fn label_us_stock(raw: Option<i32>) -> String {
272    match raw {
273        Some(3) => "LV3".to_string(),
274        other => label_standard(other),
275    }
276}
277
278fn user_attribution_region(raw: Option<i32>) -> Option<&'static str> {
279    match raw {
280        Some(1) => Some("CN"),
281        Some(2) => Some("US"),
282        Some(3) => Some("SG"),
283        Some(4) => Some("AU"),
284        Some(5) => Some("JP"),
285        Some(6) => Some("HK"),
286        _ => None,
287    }
288}