futu_mcp/handlers/reference/
warrant_ipo.rs1use 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)]
120struct IpoOut {
121 code: String,
122 name: String,
123 list_time: Option<String>,
124 list_timestamp: Option<f64>,
125
126 hk_ipo_price_min: Option<f64>,
128 hk_ipo_price_max: Option<f64>,
129 hk_list_price: Option<f64>,
131 hk_lot_size: Option<i32>,
133 hk_entrance_price: Option<f64>,
135 hk_is_subscribe_status: Option<bool>,
137 hk_apply_end_time: Option<String>,
139
140 us_ipo_price_min: Option<f64>,
142 us_ipo_price_max: Option<f64>,
143 us_issue_size: Option<i64>,
145
146 cn_ipo_price: Option<f64>,
148 cn_apply_code: Option<String>,
150 cn_issue_size: Option<i64>,
152 cn_apply_upper_limit: Option<i64>,
154 cn_industry_pe_rate: Option<f64>,
156 cn_winning_ratio: Option<f64>,
158 cn_issue_pe_rate: Option<f64>,
160 cn_apply_time: Option<String>,
162 cn_winning_time: Option<String>,
164 cn_is_has_won: Option<bool>,
166}
167
168pub async fn get_ipo_list(client: &Arc<FutuClient>, market: i32) -> Result<String> {
170 let req = futu_proto::qot_get_ipo_list::Request {
171 c2s: futu_proto::qot_get_ipo_list::C2s {
172 market,
173 header: None, },
175 };
176 let body = req.encode_to_vec();
177 let frame = client
178 .request(futu_core::proto_id::QOT_GET_IPO_LIST, body)
179 .await?;
180 let resp = futu_proto::qot_get_ipo_list::Response::decode(frame.body.as_ref())
181 .map_err(|e| anyhow!("decode ipo_list: {e}"))?;
182 if resp.ret_type != 0 {
183 bail!("ipo_list ret_type={} msg={:?}", resp.ret_type, resp.ret_msg);
184 }
185 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
186 let basic: Vec<IpoOut> = s2c
187 .ipo_list
188 .iter()
189 .map(|i| IpoOut {
190 code: i.basic.security.code.clone(),
191 name: i.basic.name.clone(),
192 list_time: i.basic.list_time.clone(),
193 list_timestamp: i.basic.list_timestamp,
194
195 hk_ipo_price_min: i.hk_ex_data.as_ref().map(|h| h.ipo_price_min),
197 hk_ipo_price_max: i.hk_ex_data.as_ref().map(|h| h.ipo_price_max),
198 hk_list_price: i.hk_ex_data.as_ref().map(|h| h.list_price),
199 hk_lot_size: i.hk_ex_data.as_ref().map(|h| h.lot_size),
200 hk_entrance_price: i.hk_ex_data.as_ref().map(|h| h.entrance_price),
201 hk_is_subscribe_status: i.hk_ex_data.as_ref().map(|h| h.is_subscribe_status),
202 hk_apply_end_time: i.hk_ex_data.as_ref().and_then(|h| h.apply_end_time.clone()),
203
204 us_ipo_price_min: i.us_ex_data.as_ref().map(|u| u.ipo_price_min),
206 us_ipo_price_max: i.us_ex_data.as_ref().map(|u| u.ipo_price_max),
207 us_issue_size: i.us_ex_data.as_ref().map(|u| u.issue_size),
208
209 cn_ipo_price: i.cn_ex_data.as_ref().map(|c| c.ipo_price),
211 cn_apply_code: i.cn_ex_data.as_ref().map(|c| c.apply_code.clone()),
212 cn_issue_size: i.cn_ex_data.as_ref().map(|c| c.issue_size),
213 cn_apply_upper_limit: i.cn_ex_data.as_ref().map(|c| c.apply_upper_limit),
214 cn_industry_pe_rate: i.cn_ex_data.as_ref().map(|c| c.industry_pe_rate),
215 cn_winning_ratio: i.cn_ex_data.as_ref().map(|c| c.winning_ratio),
216 cn_issue_pe_rate: i.cn_ex_data.as_ref().map(|c| c.issue_pe_rate),
217 cn_apply_time: i.cn_ex_data.as_ref().and_then(|c| c.apply_time.clone()),
218 cn_winning_time: i.cn_ex_data.as_ref().and_then(|c| c.winning_time.clone()),
219 cn_is_has_won: i.cn_ex_data.as_ref().map(|c| c.is_has_won),
220 })
221 .collect();
222 Ok(serde_json::to_string_pretty(&basic)?)
223}