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 sg_stock_qot_right: Option<i32>,
60    pub my_stock_qot_right: Option<i32>,
61    pub jp_future_qot_right: Option<i32>,
62    pub jp_stock_qot_right: Option<i32>,
63    pub cc_qot_right: Option<i32>,
64}
65
66pub fn profile_from_get_user_info(
67    resp: &futu_proto::get_user_info::Response,
68) -> Result<QuoteRightsProfile, String> {
69    if resp.ret_type != 0 {
70        return Err(format!(
71            "get_user_info ret_type={} msg={}",
72            resp.ret_type,
73            resp.ret_msg.as_deref().unwrap_or("")
74        ));
75    }
76    let s = resp.s2c.as_ref().ok_or_else(|| {
77        format!(
78            "get_user_info ret_type=0 but missing s2c msg={}",
79            resp.ret_msg.as_deref().unwrap_or("")
80        )
81    })?;
82
83    Ok(profile_from_snapshot(QuoteRightsSnapshot {
84        nick_name: s.nick_name.clone(),
85        user_id: s.user_id,
86        user_attribution: s.user_attribution,
87        ret_msg: resp.ret_msg.clone(),
88        sub_quota: s.sub_quota,
89        history_kl_quota: s.history_kl_quota,
90        hk_qot_right: s.hk_qot_right,
91        hk_option_qot_right: s.hk_option_qot_right,
92        hk_future_qot_right: s.hk_future_qot_right,
93        us_qot_right: s.us_qot_right,
94        us_index_qot_right: s.us_index_qot_right,
95        us_option_qot_right: s.us_option_qot_right,
96        us_cme_future_qot_right: s.us_cme_future_qot_right,
97        us_cbot_future_qot_right: s.us_cbot_future_qot_right,
98        us_nymex_future_qot_right: s.us_nymex_future_qot_right,
99        us_comex_future_qot_right: s.us_comex_future_qot_right,
100        us_cboe_future_qot_right: s.us_cboe_future_qot_right,
101        us_otc_qot_right: s.us_otc_qot_right,
102        sh_qot_right: s.sh_qot_right,
103        sz_qot_right: s.sz_qot_right,
104        sg_future_qot_right: s.sg_future_qot_right,
105        sg_stock_qot_right: s.sg_stock_qot_right,
106        my_stock_qot_right: s.my_stock_qot_right,
107        jp_future_qot_right: s.jp_future_qot_right,
108        jp_stock_qot_right: s.jp_stock_qot_right,
109        cc_qot_right: s.cc_qot_right,
110    }))
111}
112
113pub fn profile_from_snapshot(s: QuoteRightsSnapshot) -> QuoteRightsProfile {
114    let mut items = Vec::with_capacity(21);
115    push_item(
116        &mut items,
117        "香港市场",
118        "股票",
119        s.hk_qot_right,
120        label_standard,
121    );
122    push_item(
123        &mut items,
124        "香港市场",
125        "期权",
126        s.hk_option_qot_right,
127        label_standard,
128    );
129    push_item(
130        &mut items,
131        "香港市场",
132        "期货",
133        s.hk_future_qot_right,
134        label_standard,
135    );
136    push_item(
137        &mut items,
138        "美国市场",
139        "股票",
140        s.us_qot_right,
141        label_us_stock,
142    );
143    push_item(
144        &mut items,
145        "美国市场",
146        "指数",
147        s.us_index_qot_right,
148        label_standard,
149    );
150    push_item(
151        &mut items,
152        "美国市场",
153        "期权",
154        s.us_option_qot_right,
155        label_standard,
156    );
157    push_item(
158        &mut items,
159        "美国市场",
160        "CME",
161        s.us_cme_future_qot_right,
162        label_standard,
163    );
164    push_item(
165        &mut items,
166        "美国市场",
167        "CBOT",
168        s.us_cbot_future_qot_right,
169        label_standard,
170    );
171    push_item(
172        &mut items,
173        "美国市场",
174        "NYMEX",
175        s.us_nymex_future_qot_right,
176        label_standard,
177    );
178    push_item(
179        &mut items,
180        "美国市场",
181        "COMEX",
182        s.us_comex_future_qot_right,
183        label_standard,
184    );
185    push_item(
186        &mut items,
187        "美国市场",
188        "CBOE",
189        s.us_cboe_future_qot_right,
190        label_standard,
191    );
192    push_item(
193        &mut items,
194        "美国市场",
195        "OTC",
196        s.us_otc_qot_right,
197        label_standard,
198    );
199    push_item(
200        &mut items,
201        "A股市场",
202        "上证",
203        s.sh_qot_right,
204        label_standard,
205    );
206    push_item(
207        &mut items,
208        "A股市场",
209        "深证",
210        s.sz_qot_right,
211        label_standard,
212    );
213    push_item(
214        &mut items,
215        "新加坡市场",
216        "股票",
217        s.sg_stock_qot_right,
218        label_standard,
219    );
220    push_item(
221        &mut items,
222        "新加坡市场",
223        "期货",
224        s.sg_future_qot_right,
225        label_standard,
226    );
227    push_item(
228        &mut items,
229        "马来西亚市场",
230        "股票",
231        s.my_stock_qot_right,
232        label_standard,
233    );
234    push_item(
235        &mut items,
236        "日本市场",
237        "股票",
238        s.jp_stock_qot_right,
239        label_standard,
240    );
241    push_item(
242        &mut items,
243        "日本市场",
244        "期货",
245        s.jp_future_qot_right,
246        label_standard,
247    );
248    push_item(
249        &mut items,
250        "加密货币市场",
251        "加密货币",
252        s.cc_qot_right,
253        label_standard,
254    );
255
256    QuoteRightsProfile {
257        nick_name: s.nick_name,
258        user_id: s.user_id,
259        user_attribution: s.user_attribution,
260        user_attribution_region: user_attribution_region(s.user_attribution)
261            .map(ToString::to_string),
262        ret_msg: s.ret_msg,
263        quota: QuoteRightsQuota {
264            subscribe_total: s.sub_quota,
265            history_kl_total: s.history_kl_quota,
266        },
267        items,
268    }
269}
270
271fn push_item(
272    items: &mut Vec<QuoteRightItem>,
273    market: &str,
274    category: &str,
275    raw: Option<i32>,
276    label_fn: fn(Option<i32>) -> String,
277) {
278    items.push(QuoteRightItem {
279        market: market.to_string(),
280        category: category.to_string(),
281        raw,
282        label: label_fn(raw),
283    });
284}
285
286fn label_standard(raw: Option<i32>) -> String {
287    match raw {
288        None | Some(0) => "未知".to_string(),
289        Some(1) => "BMP".to_string(),
290        Some(2) => "LV1".to_string(),
291        Some(3) => "LV2".to_string(),
292        Some(4) => "SF".to_string(),
293        Some(5) => "无权限".to_string(),
294        Some(6) => "LV3".to_string(),
295        Some(v) => format!("未知({v})"),
296    }
297}
298
299fn label_us_stock(raw: Option<i32>) -> String {
300    match raw {
301        Some(3) => "LV3".to_string(),
302        other => label_standard(other),
303    }
304}
305
306fn user_attribution_region(raw: Option<i32>) -> Option<&'static str> {
307    match raw {
308        Some(1) => Some("CN"),
309        Some(2) => Some("US"),
310        Some(3) => Some("SG"),
311        Some(4) => Some("AU"),
312        Some(5) => Some("JP"),
313        Some(6) => Some("HK"),
314        _ => None,
315    }
316}