1use anyhow::{Result, bail};
2use serde::Serialize;
3use tabled::Tabled;
4
5use crate::cmd::account::{parse_trd_env, parse_trd_market_for_write};
6use crate::common::connect_gateway;
7use crate::output::OutputFormat;
8use futu_trd::misc::{get_margin_ratio, get_order_fee};
9use futu_trd::types::TrdHeader;
10
11#[derive(Tabled)]
12struct MarginRatioRow {
13 #[tabled(rename = "Code")]
14 code: String,
15 #[tabled(rename = "Long?")]
16 long: &'static str,
17 #[tabled(rename = "Short?")]
18 short: &'static str,
19 #[tabled(rename = "IM Long%")]
20 im_long: String,
21 #[tabled(rename = "IM Short%")]
22 im_short: String,
23 #[tabled(rename = "Short Pool")]
24 short_pool: String,
25 #[tabled(rename = "Short Fee%")]
26 short_fee: String,
27}
28
29#[derive(Serialize)]
30struct MarginRatioJson {
31 code: String,
32 is_long_permit: bool,
33 is_short_permit: bool,
34 im_long_ratio: f64,
35 im_short_ratio: f64,
36 short_pool_remain: f64,
37 short_fee_rate: f64,
38}
39
40pub async fn run_margin_ratio(
45 gateway: &str,
46 env: &str,
47 acc_id: u64,
48 market: &str,
49 symbols: &[String],
50 format: OutputFormat,
51) -> Result<()> {
52 let (header, secs) = build_margin_ratio_request(env, acc_id, market, symbols)?;
53 let (client, _push_rx) = connect_gateway(gateway, "futucli-margin-ratio").await?;
54 let list = get_margin_ratio(&client, &header, &secs).await?;
55
56 let rows: Vec<MarginRatioRow> = list
57 .iter()
58 .map(|r| MarginRatioRow {
59 code: r.code.clone(),
60 long: if r.is_long_permit { "yes" } else { "no" },
61 short: if r.is_short_permit { "yes" } else { "no" },
62 im_long: format!("{:.2}", r.im_long_ratio),
63 im_short: format!("{:.2}", r.im_short_ratio),
64 short_pool: format!("{:.0}", r.short_pool_remain),
65 short_fee: format!("{:.3}", r.short_fee_rate),
66 })
67 .collect();
68 let jsons: Vec<MarginRatioJson> = list
69 .iter()
70 .map(|r| MarginRatioJson {
71 code: r.code.clone(),
72 is_long_permit: r.is_long_permit,
73 is_short_permit: r.is_short_permit,
74 im_long_ratio: r.im_long_ratio,
75 im_short_ratio: r.im_short_ratio,
76 short_pool_remain: r.short_pool_remain,
77 short_fee_rate: r.short_fee_rate,
78 })
79 .collect();
80 format.print_rows(&rows, &jsons)?;
81 Ok(())
82}
83
84pub(super) fn build_margin_ratio_request(
85 env: &str,
86 acc_id: u64,
87 market: &str,
88 symbols: &[String],
89) -> Result<(TrdHeader, Vec<(i32, String)>)> {
90 if symbols.is_empty() {
91 bail!("no symbols");
92 }
93 let env_p = parse_trd_env(env)?;
94 let market_p = parse_trd_market_for_write(market)?;
95 let header = TrdHeader {
96 trd_env: env_p,
97 acc_id,
98 trd_market: market_p,
99 jp_acc_type: None,
100 };
101 let secs: Vec<(i32, String)> = symbols
103 .iter()
104 .map(|s| {
105 let sec = crate::common::parse_symbol(s)?;
106 Ok::<_, anyhow::Error>((sec.market as i32, sec.code))
108 })
109 .collect::<Result<Vec<_>>>()?;
110 Ok((header, secs))
111}
112
113#[derive(Tabled)]
114struct OrderFeeRow {
115 #[tabled(rename = "OrderID")]
116 order_id_ex: String,
117 #[tabled(rename = "Total Fee")]
118 total: String,
119 #[tabled(rename = "Items")]
120 items: String,
121}
122
123#[derive(Serialize)]
124struct OrderFeeItemJson {
125 title: String,
126 value: f64,
127}
128
129#[derive(Serialize)]
130struct OrderFeeJson {
131 order_id_ex: String,
132 fee_amount: f64,
133 fee_list: Vec<OrderFeeItemJson>,
134}
135
136pub async fn run_order_fee(
142 gateway: &str,
143 env: &str,
144 acc_id: u64,
145 market: &str,
146 order_ids: &[String],
147 format: OutputFormat,
148) -> Result<()> {
149 if order_ids.is_empty() {
150 bail!("need at least one --order-id");
151 }
152 let env_p = parse_trd_env(env)?;
153 let market_p = parse_trd_market_for_write(market)?;
154 let header = TrdHeader {
155 trd_env: env_p,
156 acc_id,
157 trd_market: market_p,
158 jp_acc_type: None,
159 };
160 let (client, _push_rx) = connect_gateway(gateway, "futucli-order-fee").await?;
161 let list = get_order_fee(&client, &header, order_ids).await?;
162
163 let rows: Vec<OrderFeeRow> = list
164 .iter()
165 .map(|f| OrderFeeRow {
166 order_id_ex: f.order_id_ex.clone(),
167 total: format!("{:.2}", f.fee_amount),
168 items: f
169 .fee_list
170 .iter()
171 .map(|i| format!("{}={:.2}", i.title, i.value))
172 .collect::<Vec<_>>()
173 .join(" | "),
174 })
175 .collect();
176 let jsons: Vec<OrderFeeJson> = list
177 .iter()
178 .map(|f| OrderFeeJson {
179 order_id_ex: f.order_id_ex.clone(),
180 fee_amount: f.fee_amount,
181 fee_list: f
182 .fee_list
183 .iter()
184 .map(|i| OrderFeeItemJson {
185 title: i.title.clone(),
186 value: i.value,
187 })
188 .collect(),
189 })
190 .collect();
191 format.print_rows(&rows, &jsons)?;
192 Ok(())
193}