1use std::sync::Arc;
5
6use anyhow::{Result, anyhow, bail};
7use futu_net::client::FutuClient;
8use futu_qot::page_bounds::validate_begin_num;
9use prost::Message;
10use serde::Serialize;
11
12use crate::state::parse_symbol;
13
14#[derive(Serialize)]
15struct WarrantOut {
16 code: String,
17 name: String,
18 owner_code: String,
19 cur_price: f64,
20 strike_price: f64,
21 maturity_time: String,
22}
23
24pub async fn get_warrant(
29 client: &Arc<FutuClient>,
30 owner_symbol: Option<&str>,
31 begin: i32,
32 num: i32,
33) -> Result<String> {
34 let bounds = validate_begin_num(begin, num, 200, "warrant").map_err(|e| anyhow!("{}", e))?;
35 let owner = match owner_symbol {
36 Some(s) => Some(parse_symbol(s)?),
37 None => None,
38 };
39 let req = futu_proto::qot_get_warrant::Request {
40 c2s: futu_proto::qot_get_warrant::C2s {
41 begin: bounds.begin,
42 num: bounds.num,
43 sort_field: 24,
45 ascend: false,
46 owner: owner.map(|s| futu_proto::qot_common::Security {
47 market: s.market as i32,
48 code: s.code,
49 }),
50 type_list: vec![],
51 issuer_list: vec![],
52 maturity_time_min: None,
53 maturity_time_max: None,
54 ipo_period: None,
55 price_type: None,
56 status: None,
57 cur_price_min: None,
58 cur_price_max: None,
59 strike_price_min: None,
60 strike_price_max: None,
61 street_min: None,
62 street_max: None,
63 conversion_min: None,
64 conversion_max: None,
65 vol_min: None,
66 vol_max: None,
67 premium_min: None,
68 premium_max: None,
69 leverage_ratio_min: None,
70 leverage_ratio_max: None,
71 delta_min: None,
72 delta_max: None,
73 implied_min: None,
74 implied_max: None,
75 recovery_price_min: None,
76 recovery_price_max: None,
77 price_recovery_ratio_min: None,
78 price_recovery_ratio_max: None,
79 header: None,
80 },
81 };
82 let body = req.encode_to_vec();
83 let frame = client
84 .request(futu_core::proto_id::QOT_GET_WARRANT, body)
85 .await?;
86 let resp = futu_proto::qot_get_warrant::Response::decode(frame.body.as_ref())
87 .map_err(|e| anyhow!("decode warrant: {e}"))?;
88 if resp.ret_type != 0 {
89 bail!("warrant ret_type={} msg={:?}", resp.ret_type, resp.ret_msg);
90 }
91 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
92 let out: Vec<WarrantOut> = s2c
93 .warrant_data_list
94 .iter()
95 .map(|w| WarrantOut {
96 code: w.stock.code.clone(),
97 name: w.name.clone(),
98 owner_code: w.owner.code.clone(),
99 cur_price: w.cur_price,
100 strike_price: w.strike_price,
101 maturity_time: w.maturity_time.clone(),
102 })
103 .collect();
104 Ok(serde_json::to_string_pretty(&serde_json::json!({
105 "last_page": s2c.last_page,
106 "all_count": s2c.all_count,
107 "warrant_list": out,
108 }))?)
109}
110
111#[derive(Serialize)]
121struct IpoOut {
122 code: String,
123 name: String,
124 list_time: Option<String>,
125 list_timestamp: Option<f64>,
126
127 hk_ipo_price_min: Option<f64>,
129 hk_ipo_price_max: Option<f64>,
130 hk_list_price: Option<f64>,
132 hk_lot_size: Option<i32>,
134 hk_entrance_price: Option<f64>,
136 hk_is_subscribe_status: Option<bool>,
138 hk_apply_end_time: Option<String>,
140
141 us_ipo_price_min: Option<f64>,
143 us_ipo_price_max: Option<f64>,
144 us_issue_size: Option<i64>,
146
147 cn_ipo_price: Option<f64>,
149 cn_apply_code: Option<String>,
151 cn_issue_size: Option<i64>,
153 cn_apply_upper_limit: Option<i64>,
155 cn_industry_pe_rate: Option<f64>,
157 cn_winning_ratio: Option<f64>,
159 cn_issue_pe_rate: Option<f64>,
161 cn_apply_time: Option<String>,
163 cn_winning_time: Option<String>,
165 cn_is_has_won: Option<bool>,
167
168 sg_ipo_price_min: Option<f64>,
170 sg_ipo_price_max: Option<f64>,
171 sg_issue_size: Option<i64>,
172 sg_apply_start_time: Option<String>,
173 sg_apply_end_time: Option<String>,
174 sg_winning_time: Option<String>,
175
176 my_offer_price: Option<f64>,
178 my_issue_size: Option<i64>,
179 my_apply_start_time: Option<String>,
180 my_apply_end_time: Option<String>,
181 my_winning_time: Option<String>,
182
183 jp_ipo_price_min: Option<f64>,
185 jp_ipo_price_max: Option<f64>,
186 jp_issue_size: Option<i64>,
187 jp_lot_size: Option<i32>,
188 jp_eqty_issued_shares: Option<i64>,
189 jp_isin: Option<String>,
190 jp_issued_shares: Option<i64>,
191 jp_industry: Option<String>,
192 jp_market_segment: Option<String>,
193 jp_approval_time: Option<String>,
194 jp_approval_timestamp: Option<f64>,
195 jp_issue_confirm_time: Option<String>,
196 jp_issue_confirm_timestamp: Option<f64>,
197 jp_price_confirm_start_time: Option<String>,
198 jp_price_confirm_start_timestamp: Option<f64>,
199 jp_price_confirm_end_time: Option<String>,
200 jp_price_confirm_end_timestamp: Option<f64>,
201 jp_inquiry_start_time: Option<String>,
202 jp_inquiry_start_timestamp: Option<f64>,
203 jp_inquiry_end_time: Option<String>,
204 jp_inquiry_end_timestamp: Option<f64>,
205 jp_apply_start_time: Option<String>,
206 jp_apply_start_timestamp: Option<f64>,
207 jp_apply_end_time: Option<String>,
208 jp_apply_end_timestamp: Option<f64>,
209 jp_draw_time: Option<String>,
210 jp_draw_timestamp: Option<f64>,
211 jp_winning_time: Option<String>,
212 jp_winning_timestamp: Option<f64>,
213 jp_etf_management_fee_rates: Option<i64>,
214 jp_etf_dividend_times: Option<i64>,
215 jp_etf_dividend_frequency_type: Option<i32>,
216 jp_etf_investing_risk_type: Option<i32>,
217 jp_etf_index_name: Option<String>,
218 jp_etf_company_name: Option<String>,
219 jp_etf_company_introduction_link: Option<String>,
220 jp_etf_company_interview_link: Option<String>,
221 jp_etf_pamphlet_link: Option<String>,
222 jp_etf_introduction_link: Option<String>,
223}
224
225pub async fn get_ipo_list(client: &Arc<FutuClient>, market: i32) -> Result<String> {
227 let req = futu_proto::qot_get_ipo_list::Request {
228 c2s: futu_proto::qot_get_ipo_list::C2s {
229 market,
230 header: None, },
232 };
233 let body = req.encode_to_vec();
234 let frame = client
235 .request(futu_core::proto_id::QOT_GET_IPO_LIST, body)
236 .await?;
237 let resp = futu_proto::qot_get_ipo_list::Response::decode(frame.body.as_ref())
238 .map_err(|e| anyhow!("decode ipo_list: {e}"))?;
239 if resp.ret_type != 0 {
240 bail!("ipo_list ret_type={} msg={:?}", resp.ret_type, resp.ret_msg);
241 }
242 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
243 let basic: Vec<IpoOut> = s2c
244 .ipo_list
245 .iter()
246 .map(|i| IpoOut {
247 code: i.basic.security.code.clone(),
248 name: i.basic.name.clone(),
249 list_time: i.basic.list_time.clone(),
250 list_timestamp: i.basic.list_timestamp,
251
252 hk_ipo_price_min: i.hk_ex_data.as_ref().map(|h| h.ipo_price_min),
254 hk_ipo_price_max: i.hk_ex_data.as_ref().map(|h| h.ipo_price_max),
255 hk_list_price: i.hk_ex_data.as_ref().map(|h| h.list_price),
256 hk_lot_size: i.hk_ex_data.as_ref().map(|h| h.lot_size),
257 hk_entrance_price: i.hk_ex_data.as_ref().map(|h| h.entrance_price),
258 hk_is_subscribe_status: i.hk_ex_data.as_ref().map(|h| h.is_subscribe_status),
259 hk_apply_end_time: i.hk_ex_data.as_ref().and_then(|h| h.apply_end_time.clone()),
260
261 us_ipo_price_min: i.us_ex_data.as_ref().map(|u| u.ipo_price_min),
263 us_ipo_price_max: i.us_ex_data.as_ref().map(|u| u.ipo_price_max),
264 us_issue_size: i.us_ex_data.as_ref().map(|u| u.issue_size),
265
266 cn_ipo_price: i.cn_ex_data.as_ref().map(|c| c.ipo_price),
268 cn_apply_code: i.cn_ex_data.as_ref().map(|c| c.apply_code.clone()),
269 cn_issue_size: i.cn_ex_data.as_ref().map(|c| c.issue_size),
270 cn_apply_upper_limit: i.cn_ex_data.as_ref().map(|c| c.apply_upper_limit),
271 cn_industry_pe_rate: i.cn_ex_data.as_ref().map(|c| c.industry_pe_rate),
272 cn_winning_ratio: i.cn_ex_data.as_ref().map(|c| c.winning_ratio),
273 cn_issue_pe_rate: i.cn_ex_data.as_ref().map(|c| c.issue_pe_rate),
274 cn_apply_time: i.cn_ex_data.as_ref().and_then(|c| c.apply_time.clone()),
275 cn_winning_time: i.cn_ex_data.as_ref().and_then(|c| c.winning_time.clone()),
276 cn_is_has_won: i.cn_ex_data.as_ref().map(|c| c.is_has_won),
277
278 sg_ipo_price_min: i.sg_ex_data.as_ref().map(|s| s.ipo_price_min),
280 sg_ipo_price_max: i.sg_ex_data.as_ref().map(|s| s.ipo_price_max),
281 sg_issue_size: i.sg_ex_data.as_ref().map(|s| s.issue_size),
282 sg_apply_start_time: i
283 .sg_ex_data
284 .as_ref()
285 .and_then(|s| s.apply_start_time.clone()),
286 sg_apply_end_time: i.sg_ex_data.as_ref().and_then(|s| s.apply_end_time.clone()),
287 sg_winning_time: i.sg_ex_data.as_ref().and_then(|s| s.winning_time.clone()),
288
289 my_offer_price: i.my_ex_data.as_ref().map(|m| m.offer_price),
291 my_issue_size: i.my_ex_data.as_ref().map(|m| m.issue_size),
292 my_apply_start_time: i
293 .my_ex_data
294 .as_ref()
295 .and_then(|m| m.apply_start_time.clone()),
296 my_apply_end_time: i.my_ex_data.as_ref().and_then(|m| m.apply_end_time.clone()),
297 my_winning_time: i.my_ex_data.as_ref().and_then(|m| m.winning_time.clone()),
298
299 jp_ipo_price_min: i.jp_ex_data.as_ref().map(|j| j.ipo_price_min),
301 jp_ipo_price_max: i.jp_ex_data.as_ref().map(|j| j.ipo_price_max),
302 jp_issue_size: i.jp_ex_data.as_ref().map(|j| j.issue_size),
303 jp_lot_size: i.jp_ex_data.as_ref().and_then(|j| j.lot_size),
304 jp_eqty_issued_shares: i.jp_ex_data.as_ref().and_then(|j| j.eqty_issued_shares),
305 jp_isin: i.jp_ex_data.as_ref().and_then(|j| j.isin.clone()),
306 jp_issued_shares: i.jp_ex_data.as_ref().and_then(|j| j.issued_shares),
307 jp_industry: i.jp_ex_data.as_ref().and_then(|j| j.industry.clone()),
308 jp_market_segment: i.jp_ex_data.as_ref().and_then(|j| j.market_segment.clone()),
309 jp_approval_time: i.jp_ex_data.as_ref().and_then(|j| j.approval_time.clone()),
310 jp_approval_timestamp: i.jp_ex_data.as_ref().and_then(|j| j.approval_timestamp),
311 jp_issue_confirm_time: i
312 .jp_ex_data
313 .as_ref()
314 .and_then(|j| j.issue_confirm_time.clone()),
315 jp_issue_confirm_timestamp: i
316 .jp_ex_data
317 .as_ref()
318 .and_then(|j| j.issue_confirm_timestamp),
319 jp_price_confirm_start_time: i
320 .jp_ex_data
321 .as_ref()
322 .and_then(|j| j.price_confirm_start_time.clone()),
323 jp_price_confirm_start_timestamp: i
324 .jp_ex_data
325 .as_ref()
326 .and_then(|j| j.price_confirm_start_timestamp),
327 jp_price_confirm_end_time: i
328 .jp_ex_data
329 .as_ref()
330 .and_then(|j| j.price_confirm_end_time.clone()),
331 jp_price_confirm_end_timestamp: i
332 .jp_ex_data
333 .as_ref()
334 .and_then(|j| j.price_confirm_end_timestamp),
335 jp_inquiry_start_time: i
336 .jp_ex_data
337 .as_ref()
338 .and_then(|j| j.inquiry_start_time.clone()),
339 jp_inquiry_start_timestamp: i
340 .jp_ex_data
341 .as_ref()
342 .and_then(|j| j.inquiry_start_timestamp),
343 jp_inquiry_end_time: i
344 .jp_ex_data
345 .as_ref()
346 .and_then(|j| j.inquiry_end_time.clone()),
347 jp_inquiry_end_timestamp: i.jp_ex_data.as_ref().and_then(|j| j.inquiry_end_timestamp),
348 jp_apply_start_time: i
349 .jp_ex_data
350 .as_ref()
351 .and_then(|j| j.apply_start_time.clone()),
352 jp_apply_start_timestamp: i.jp_ex_data.as_ref().and_then(|j| j.apply_start_timestamp),
353 jp_apply_end_time: i.jp_ex_data.as_ref().and_then(|j| j.apply_end_time.clone()),
354 jp_apply_end_timestamp: i.jp_ex_data.as_ref().and_then(|j| j.apply_end_timestamp),
355 jp_draw_time: i.jp_ex_data.as_ref().and_then(|j| j.draw_time.clone()),
356 jp_draw_timestamp: i.jp_ex_data.as_ref().and_then(|j| j.draw_timestamp),
357 jp_winning_time: i.jp_ex_data.as_ref().and_then(|j| j.winning_time.clone()),
358 jp_winning_timestamp: i.jp_ex_data.as_ref().and_then(|j| j.winning_timestamp),
359 jp_etf_management_fee_rates: i
360 .jp_ex_data
361 .as_ref()
362 .and_then(|j| j.etf_info.as_ref())
363 .and_then(|e| e.management_fee_rates),
364 jp_etf_dividend_times: i
365 .jp_ex_data
366 .as_ref()
367 .and_then(|j| j.etf_info.as_ref())
368 .and_then(|e| e.dividend_times),
369 jp_etf_dividend_frequency_type: i
370 .jp_ex_data
371 .as_ref()
372 .and_then(|j| j.etf_info.as_ref())
373 .and_then(|e| e.dividend_frequency_type),
374 jp_etf_investing_risk_type: i
375 .jp_ex_data
376 .as_ref()
377 .and_then(|j| j.etf_info.as_ref())
378 .and_then(|e| e.investing_risk_type),
379 jp_etf_index_name: i
380 .jp_ex_data
381 .as_ref()
382 .and_then(|j| j.etf_info.as_ref())
383 .and_then(|e| e.index_name.clone()),
384 jp_etf_company_name: i
385 .jp_ex_data
386 .as_ref()
387 .and_then(|j| j.etf_info.as_ref())
388 .and_then(|e| e.company_name.clone()),
389 jp_etf_company_introduction_link: i
390 .jp_ex_data
391 .as_ref()
392 .and_then(|j| j.etf_info.as_ref())
393 .and_then(|e| e.company_introduction_link.clone()),
394 jp_etf_company_interview_link: i
395 .jp_ex_data
396 .as_ref()
397 .and_then(|j| j.etf_info.as_ref())
398 .and_then(|e| e.company_interview_link.clone()),
399 jp_etf_pamphlet_link: i
400 .jp_ex_data
401 .as_ref()
402 .and_then(|j| j.etf_info.as_ref())
403 .and_then(|e| e.etf_pamphlet_link.clone()),
404 jp_etf_introduction_link: i
405 .jp_ex_data
406 .as_ref()
407 .and_then(|j| j.etf_info.as_ref())
408 .and_then(|e| e.etf_introduction_link.clone()),
409 })
410 .collect();
411 Ok(serde_json::to_string_pretty(&basic)?)
412}