1use 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}