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
13#[derive(Tabled)]
18struct CapitalFlowRow {
19 #[tabled(rename = "Time")]
20 time: String,
21 #[tabled(rename = "In Flow")]
22 in_flow: String,
23}
24
25#[derive(Serialize)]
26struct CapitalFlowJson {
27 symbol: String,
28 period_type: i32,
29 last_valid_time: String,
30 flow_item_list: Vec<CapitalFlowItemJson>,
31}
32
33#[derive(Serialize)]
34struct CapitalFlowItemJson {
35 timestamp: f64,
36 in_flow: f64,
37}
38
39pub async fn run_capital_flow(
40 gateway: &str,
41 symbol: &str,
42 period_type: i32,
43 begin: Option<&str>,
44 end: Option<&str>,
45 format: OutputFormat,
46) -> Result<()> {
47 let sec = parse_symbol(symbol)?;
48 let (client, _rx) = connect_gateway(gateway, "futucli-capital-flow").await?;
49
50 let req = futu_proto::qot_get_capital_flow::Request {
51 c2s: futu_proto::qot_get_capital_flow::C2s {
52 security: futu_proto::qot_common::Security {
53 market: sec.market as i32,
54 code: sec.code.clone(),
55 },
56 period_type: Some(period_type),
57 begin_time: begin.map(|s| s.to_string()),
58 end_time: end.map(|s| s.to_string()),
59 header: None, },
61 };
62 let body = req.encode_to_vec();
63 let frame = client
64 .request(futu_core::proto_id::QOT_GET_CAPITAL_FLOW, body)
65 .await?;
66 let resp = futu_proto::qot_get_capital_flow::Response::decode(frame.body.as_ref())?;
67 if resp.ret_type != 0 {
68 bail!(
69 "capital_flow ret_type={} msg={:?}",
70 resp.ret_type,
71 resp.ret_msg
72 );
73 }
74 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
75
76 let rows: Vec<CapitalFlowRow> = s2c
77 .flow_item_list
78 .iter()
79 .map(|f| CapitalFlowRow {
80 time: ts_to_datetime(f.timestamp.unwrap_or(0.0)),
81 in_flow: format!("{:.2}", f.in_flow),
82 })
83 .collect();
84
85 let json = CapitalFlowJson {
86 symbol: symbol.to_string(),
87 period_type,
88 last_valid_time: s2c.last_valid_time.unwrap_or_default(),
89 flow_item_list: s2c
90 .flow_item_list
91 .iter()
92 .map(|f| CapitalFlowItemJson {
93 timestamp: f.timestamp.unwrap_or(0.0),
94 in_flow: f.in_flow,
95 })
96 .collect(),
97 };
98 format.print_rows(&rows, &[json])?;
99 Ok(())
100}
101
102fn ts_to_datetime(ts: f64) -> String {
103 let secs = ts as i64;
104 chrono::DateTime::<chrono::Local>::from(
105 std::time::UNIX_EPOCH + std::time::Duration::from_secs(secs as u64),
106 )
107 .format("%Y-%m-%d %H:%M:%S")
108 .to_string()
109}
110
111#[derive(Tabled)]
116struct CapitalDistRow {
117 #[tabled(rename = "Tier")]
118 tier: String,
119 #[tabled(rename = "In")]
120 in_amount: String,
121 #[tabled(rename = "Out")]
122 out_amount: String,
123 #[tabled(rename = "Net")]
124 net: String,
125}
126
127#[derive(Serialize)]
128struct CapitalDistJson {
129 symbol: String,
130 capital_in_super: f64,
131 capital_in_big: f64,
132 capital_in_mid: f64,
133 capital_in_small: f64,
134 capital_out_super: f64,
135 capital_out_big: f64,
136 capital_out_mid: f64,
137 capital_out_small: f64,
138 update_time: String,
139}
140
141pub async fn run_capital_distribution(
142 gateway: &str,
143 symbol: &str,
144 format: OutputFormat,
145) -> Result<()> {
146 let sec = parse_symbol(symbol)?;
147 let (client, _rx) = connect_gateway(gateway, "futucli-capital-dist").await?;
148
149 let req = futu_proto::qot_get_capital_distribution::Request {
150 c2s: futu_proto::qot_get_capital_distribution::C2s {
151 security: futu_proto::qot_common::Security {
152 market: sec.market as i32,
153 code: sec.code.clone(),
154 },
155 header: None, },
157 };
158 let body = req.encode_to_vec();
159 let frame = client
160 .request(futu_core::proto_id::QOT_GET_CAPITAL_DISTRIBUTION, body)
161 .await?;
162 let resp = futu_proto::qot_get_capital_distribution::Response::decode(frame.body.as_ref())?;
163 if resp.ret_type != 0 {
164 bail!(
165 "capital_distribution ret_type={} msg={:?}",
166 resp.ret_type,
167 resp.ret_msg
168 );
169 }
170 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
171
172 let super_in = s.capital_in_super.unwrap_or(0.0);
173 let super_out = s.capital_out_super.unwrap_or(0.0);
174 let rows = vec![
175 CapitalDistRow {
176 tier: "Super".into(),
177 in_amount: format!("{super_in:.2}"),
178 out_amount: format!("{super_out:.2}"),
179 net: format!("{:.2}", super_in - super_out),
180 },
181 CapitalDistRow {
182 tier: "Big".into(),
183 in_amount: format!("{:.2}", s.capital_in_big),
184 out_amount: format!("{:.2}", s.capital_out_big),
185 net: format!("{:.2}", s.capital_in_big - s.capital_out_big),
186 },
187 CapitalDistRow {
188 tier: "Mid".into(),
189 in_amount: format!("{:.2}", s.capital_in_mid),
190 out_amount: format!("{:.2}", s.capital_out_mid),
191 net: format!("{:.2}", s.capital_in_mid - s.capital_out_mid),
192 },
193 CapitalDistRow {
194 tier: "Small".into(),
195 in_amount: format!("{:.2}", s.capital_in_small),
196 out_amount: format!("{:.2}", s.capital_out_small),
197 net: format!("{:.2}", s.capital_in_small - s.capital_out_small),
198 },
199 ];
200
201 let json = CapitalDistJson {
202 symbol: symbol.to_string(),
203 capital_in_super: super_in,
204 capital_in_big: s.capital_in_big,
205 capital_in_mid: s.capital_in_mid,
206 capital_in_small: s.capital_in_small,
207 capital_out_super: super_out,
208 capital_out_big: s.capital_out_big,
209 capital_out_mid: s.capital_out_mid,
210 capital_out_small: s.capital_out_small,
211 update_time: s.update_time.unwrap_or_default(),
212 };
213 format.print_rows(&rows, &[json])?;
214 Ok(())
215}