1use anyhow::{Result, anyhow, bail};
6use prost::Message;
7use serde::Serialize;
8use tabled::Tabled;
9
10use crate::common::{connect_gateway, parse_symbol};
11use crate::output::OutputFormat;
12
13use super::trading::parse_qot_market;
14
15#[derive(Tabled)]
16struct WarrantRow {
17 #[tabled(rename = "Code")]
18 code: String,
19 #[tabled(rename = "Name")]
20 name: String,
21 #[tabled(rename = "Owner")]
22 owner: String,
23 #[tabled(rename = "Cur Price")]
24 cur_price: String,
25 #[tabled(rename = "Strike")]
26 strike: String,
27 #[tabled(rename = "Maturity")]
28 maturity: String,
29}
30
31#[derive(Serialize)]
32struct WarrantJson {
33 code: String,
34 name: String,
35 owner_code: String,
36 cur_price: f64,
37 strike_price: f64,
38 maturity_time: String,
39}
40
41pub async fn run_warrant(
42 gateway: &str,
43 owner_symbol: Option<&str>,
44 begin: i32,
45 num: i32,
46 format: OutputFormat,
47) -> Result<()> {
48 let bounds = futu_qot::page_bounds::validate_begin_num(begin, num, 200, "warrant")
51 .map_err(|e| anyhow!("{}", e))?;
52 let owner = match owner_symbol {
53 Some(s) => Some(parse_symbol(s)?),
54 None => None,
55 };
56 let (client, _rx) = connect_gateway(gateway, "futucli-warrant").await?;
57 let req = futu_proto::qot_get_warrant::Request {
58 c2s: futu_proto::qot_get_warrant::C2s {
59 begin: bounds.begin,
60 num: bounds.num,
61 sort_field: 24, ascend: false,
63 owner: owner.map(|s| futu_proto::qot_common::Security {
64 market: s.market as i32,
65 code: s.code,
66 }),
67 type_list: vec![],
68 issuer_list: vec![],
69 maturity_time_min: None,
70 maturity_time_max: None,
71 ipo_period: None,
72 price_type: None,
73 status: None,
74 cur_price_min: None,
75 cur_price_max: None,
76 strike_price_min: None,
77 strike_price_max: None,
78 street_min: None,
79 street_max: None,
80 conversion_min: None,
81 conversion_max: None,
82 vol_min: None,
83 vol_max: None,
84 premium_min: None,
85 premium_max: None,
86 leverage_ratio_min: None,
87 leverage_ratio_max: None,
88 delta_min: None,
89 delta_max: None,
90 implied_min: None,
91 implied_max: None,
92 recovery_price_min: None,
93 recovery_price_max: None,
94 price_recovery_ratio_min: None,
95 price_recovery_ratio_max: None,
96 header: None,
97 },
98 };
99 let body = req.encode_to_vec();
100 let frame = client
101 .request(futu_core::proto_id::QOT_GET_WARRANT, body)
102 .await?;
103 let resp = futu_proto::qot_get_warrant::Response::decode(frame.body.as_ref())
104 .map_err(|e| anyhow!("decode warrant: {e}"))?;
105 if resp.ret_type != 0 {
106 bail!("warrant ret_type={} msg={:?}", resp.ret_type, resp.ret_msg);
107 }
108 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
109 let mut rows = Vec::new();
110 let mut jsons = Vec::new();
111 for w in &s2c.warrant_data_list {
112 rows.push(WarrantRow {
113 code: w.stock.code.clone(),
114 name: w.name.clone(),
115 owner: w.owner.code.clone(),
116 cur_price: format!("{:.3}", w.cur_price),
117 strike: format!("{:.3}", w.strike_price),
118 maturity: w.maturity_time.clone(),
119 });
120 jsons.push(WarrantJson {
121 code: w.stock.code.clone(),
122 name: w.name.clone(),
123 owner_code: w.owner.code.clone(),
124 cur_price: w.cur_price,
125 strike_price: w.strike_price,
126 maturity_time: w.maturity_time.clone(),
127 });
128 }
129 format.print_rows(&rows, &jsons)?;
130 Ok(())
131}
132
133#[derive(Tabled)]
134struct IpoRow {
135 #[tabled(rename = "Code")]
136 code: String,
137 #[tabled(rename = "Name")]
138 name: String,
139 #[tabled(rename = "List Time")]
140 list_time: String,
141}
142
143#[derive(Serialize)]
144struct IpoJson {
145 code: String,
146 name: String,
147 list_time: Option<String>,
148}
149
150pub async fn run_ipo_list(gateway: &str, market: &str, format: OutputFormat) -> Result<()> {
151 let m = parse_qot_market(market)?;
152 let (client, _rx) = connect_gateway(gateway, "futucli-ipo-list").await?;
153 let req = futu_proto::qot_get_ipo_list::Request {
154 c2s: futu_proto::qot_get_ipo_list::C2s {
155 market: m,
156 header: None, },
158 };
159 let body = req.encode_to_vec();
160 let frame = client
161 .request(futu_core::proto_id::QOT_GET_IPO_LIST, body)
162 .await?;
163 let resp = futu_proto::qot_get_ipo_list::Response::decode(frame.body.as_ref())
164 .map_err(|e| anyhow!("decode ipo_list: {e}"))?;
165 if resp.ret_type != 0 {
166 bail!("ipo_list ret_type={} msg={:?}", resp.ret_type, resp.ret_msg);
167 }
168 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
169 let mut rows = Vec::new();
170 let mut jsons = Vec::new();
171 for i in &s2c.ipo_list {
172 rows.push(IpoRow {
173 code: i.basic.security.code.clone(),
174 name: i.basic.name.clone(),
175 list_time: i.basic.list_time.clone().unwrap_or_else(|| "-".into()),
176 });
177 jsons.push(IpoJson {
178 code: i.basic.security.code.clone(),
179 name: i.basic.name.clone(),
180 list_time: i.basic.list_time.clone(),
181 });
182 }
183 format.print_rows(&rows, &jsons)?;
184 Ok(())
185}