Skip to main content

futucli/cli/dispatch/
trade_write.rs

1use anyhow::Result;
2
3use super::super::commands::{
4    AccCashFlowArgs, CancelOrderArgs, HistoryDealsArgs, HistoryOrdersArgs, MarginRatioArgs,
5    MaxQtysArgs, ModifyOrderArgs, OrderFeeArgs, PlaceOrderArgs, ReconfirmOrderArgs,
6};
7use crate::cmd;
8use crate::output::OutputFormat;
9
10pub(super) struct CancelAllOrderDispatchArgs {
11    pub(super) acc_id: Option<u64>,
12    pub(super) acc_id_file: Option<std::path::PathBuf>,
13    pub(super) card_num: Option<String>,
14    pub(super) env: String,
15    pub(super) market: Option<String>,
16    pub(super) jp_acc_type: Option<i32>,
17    pub(super) confirm: bool,
18}
19
20fn csv_list(value: Option<String>) -> Vec<String> {
21    value
22        .map(|s| {
23            s.split(',')
24                .map(|x| x.trim().to_string())
25                .filter(|x| !x.is_empty())
26                .collect()
27        })
28        .unwrap_or_default()
29}
30
31pub(super) async fn dispatch_place_order(
32    gateway: &str,
33    output: OutputFormat,
34    args: PlaceOrderArgs,
35) -> Result<()> {
36    let acc_id = cmd::account::resolve_account_locator(
37        gateway,
38        args.acc_id,
39        args.acc_id_file.as_deref(),
40        args.card_num.as_deref(),
41        "place-order",
42    )
43    .await?;
44    cmd::trade_ext::run_place_order(cmd::trade_ext::PlaceOrderCommand {
45        gateway,
46        env: &args.env,
47        acc_id,
48        market: &args.market,
49        side: &args.side,
50        order_type: &args.order_type,
51        code: &args.code,
52        qty: args.qty,
53        price: args.price,
54        amount: args.amount,
55        pred_side: args.pred_side,
56        time_in_force: args.time_in_force.as_deref(),
57        fill_outside_rth: args.fill_outside_rth,
58        session: args.session.as_deref(),
59        expire_time: args.expire_time.as_deref(),
60        jp_acc_type: args.jp_acc_type,
61        confirm: args.confirm,
62        idempotency_key: args.idempotency_key,
63        stop_price: args.stop_price,
64        trail_type: args.trail_type,
65        trail_value: args.trail_value,
66        trail_spread: args.trail_spread,
67        output,
68    })
69    .await
70}
71
72pub(super) async fn dispatch_modify_order(
73    gateway: &str,
74    output: OutputFormat,
75    args: ModifyOrderArgs,
76) -> Result<()> {
77    let acc_id = cmd::account::resolve_account_locator(
78        gateway,
79        args.acc_id,
80        args.acc_id_file.as_deref(),
81        args.card_num.as_deref(),
82        "modify-order",
83    )
84    .await?;
85    cmd::trade_ext::run_modify_order(cmd::trade_ext::ModifyOrderCommand {
86        gateway,
87        env: &args.env,
88        acc_id,
89        market: &args.market,
90        order_id: args.order_id,
91        op: &args.op,
92        qty: args.qty,
93        price: args.price,
94        jp_acc_type: args.jp_acc_type,
95        confirm: args.confirm,
96        idempotency_key: args.idempotency_key,
97        output,
98    })
99    .await
100}
101
102pub(super) async fn dispatch_cancel_order(
103    gateway: &str,
104    output: OutputFormat,
105    args: CancelOrderArgs,
106) -> Result<()> {
107    let acc_id = cmd::account::resolve_account_locator(
108        gateway,
109        args.acc_id,
110        args.acc_id_file.as_deref(),
111        args.card_num.as_deref(),
112        "cancel-order",
113    )
114    .await?;
115    cmd::trade_ext::run_cancel_order(cmd::trade_ext::CancelOrderCommand {
116        gateway,
117        env: &args.env,
118        acc_id,
119        market: &args.market,
120        order_id: args.order_id,
121        jp_acc_type: args.jp_acc_type,
122        confirm: args.confirm,
123        idempotency_key: args.idempotency_key,
124        output,
125    })
126    .await
127}
128
129pub(super) async fn dispatch_reconfirm_order(
130    gateway: &str,
131    output: OutputFormat,
132    args: ReconfirmOrderArgs,
133) -> Result<()> {
134    let acc_id = cmd::account::resolve_account_locator(
135        gateway,
136        args.acc_id,
137        args.acc_id_file.as_deref(),
138        args.card_num.as_deref(),
139        "reconfirm-order",
140    )
141    .await?;
142    cmd::trade_ext::run_reconfirm_order(cmd::trade_ext::ReconfirmOrderCommand {
143        gateway,
144        env: &args.env,
145        acc_id,
146        market: &args.market,
147        order_id: args.order_id,
148        reason: args.reason,
149        jp_acc_type: args.jp_acc_type,
150        confirm: args.confirm,
151        output,
152    })
153    .await
154}
155
156pub(super) async fn dispatch_history_orders(
157    gateway: &str,
158    output: OutputFormat,
159    args: HistoryOrdersArgs,
160) -> Result<()> {
161    let acc_id = cmd::account::resolve_account_locator(
162        gateway,
163        args.acc_id,
164        args.acc_id_file.as_deref(),
165        args.card_num.as_deref(),
166        "history-orders",
167    )
168    .await?;
169    cmd::trade_ext::run_history_orders(cmd::trade_ext::HistoryOrdersCommand {
170        gateway,
171        env: &args.env,
172        acc_id,
173        market: &args.market,
174        codes: csv_list(args.codes),
175        begin: args.begin,
176        end: args.end,
177        output,
178    })
179    .await
180}
181
182pub(super) async fn dispatch_history_deals(
183    gateway: &str,
184    output: OutputFormat,
185    args: HistoryDealsArgs,
186) -> Result<()> {
187    let acc_id = cmd::account::resolve_account_locator(
188        gateway,
189        args.acc_id,
190        args.acc_id_file.as_deref(),
191        args.card_num.as_deref(),
192        "history-deals",
193    )
194    .await?;
195    cmd::trade_ext::run_history_deals(cmd::trade_ext::HistoryDealsCommand {
196        gateway,
197        env: &args.env,
198        acc_id,
199        market: &args.market,
200        codes: csv_list(args.codes),
201        begin: args.begin,
202        end: args.end,
203        output,
204    })
205    .await
206}
207
208pub(super) async fn dispatch_max_qtys(
209    gateway: &str,
210    output: OutputFormat,
211    args: MaxQtysArgs,
212) -> Result<()> {
213    let acc_id = cmd::account::resolve_account_locator(
214        gateway,
215        args.acc_id,
216        args.acc_id_file.as_deref(),
217        args.card_num.as_deref(),
218        "max-trd-qtys",
219    )
220    .await?;
221    cmd::trade_ext::run_max_qtys(cmd::trade_ext::MaxQtysCommand {
222        gateway,
223        env: &args.env,
224        acc_id,
225        market: &args.market,
226        order_type: &args.order_type,
227        code: &args.code,
228        price: args.price,
229        jp_acc_type: args.jp_acc_type,
230        output,
231    })
232    .await
233}
234
235pub(super) async fn dispatch_margin_ratio(
236    gateway: &str,
237    output: OutputFormat,
238    args: MarginRatioArgs,
239) -> Result<()> {
240    let acc_id = cmd::account::resolve_account_locator(
241        gateway,
242        args.acc_id,
243        args.acc_id_file.as_deref(),
244        args.card_num.as_deref(),
245        "margin-ratio",
246    )
247    .await?;
248    let symbols = args.symbols.or(args.symbols_arg).ok_or_else(|| {
249        anyhow::anyhow!("margin-ratio: 需要位置参数 <SYMBOLS> 或 --code / --symbols")
250    })?;
251    let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
252    cmd::trade_ext::run_margin_ratio(gateway, &args.env, acc_id, &args.market, &syms, output).await
253}
254
255pub(super) async fn dispatch_order_fee(
256    gateway: &str,
257    output: OutputFormat,
258    args: OrderFeeArgs,
259) -> Result<()> {
260    let acc_id = cmd::account::resolve_account_locator(
261        gateway,
262        args.acc_id,
263        args.acc_id_file.as_deref(),
264        args.card_num.as_deref(),
265        "order-fee",
266    )
267    .await?;
268    let ids: Vec<String> = args
269        .order_ids
270        .split(',')
271        .map(|s| s.trim().to_string())
272        .collect();
273    cmd::trade_ext::run_order_fee(gateway, &args.env, acc_id, &args.market, &ids, output).await
274}
275
276pub(super) async fn dispatch_cancel_all_order(
277    gateway: &str,
278    output: OutputFormat,
279    args: CancelAllOrderDispatchArgs,
280) -> Result<()> {
281    let acc_id = cmd::account::resolve_account_locator(
282        gateway,
283        args.acc_id,
284        args.acc_id_file.as_deref(),
285        args.card_num.as_deref(),
286        "cancel-all-order",
287    )
288    .await?;
289    cmd::trade_ext::run_cancel_all_order(
290        gateway,
291        acc_id,
292        &args.env,
293        args.market.as_deref(),
294        args.jp_acc_type,
295        args.confirm,
296        output,
297    )
298    .await
299}
300
301pub(super) async fn dispatch_acc_cash_flow(
302    gateway: &str,
303    output: OutputFormat,
304    args: AccCashFlowArgs,
305) -> Result<()> {
306    let acc_id = cmd::account::resolve_account_locator(
307        gateway,
308        args.acc_id.or(args.acc_id_arg),
309        args.acc_id_file.as_deref(),
310        args.card_num.as_deref(),
311        "acc-cash-flow",
312    )
313    .await?;
314    // date / date_range 通过 #[arg(conflicts_with)] 互斥
315    if let Some(range) = args.date_range {
316        // 解析 "YYYY-MM-DD..YYYY-MM-DD"
317        let (from_s, to_s) = range.split_once("..").ok_or_else(|| {
318            anyhow::anyhow!("--date-range 格式应为 `YYYY-MM-DD..YYYY-MM-DD`,当前:{range}")
319        })?;
320        let from = chrono::NaiveDate::parse_from_str(from_s, "%Y-%m-%d")
321            .map_err(|e| anyhow::anyhow!("start date parse: {e}"))?;
322        let to = chrono::NaiveDate::parse_from_str(to_s, "%Y-%m-%d")
323            .map_err(|e| anyhow::anyhow!("end date parse: {e}"))?;
324        cmd::trade_ext::run_acc_cash_flow_range(cmd::trade_ext::AccCashFlowRangeCommand {
325            gateway,
326            acc_id,
327            date_from: from,
328            date_to: to,
329            env: &args.env,
330            market: &args.market,
331            direction: args.direction,
332        })
333        .await
334    } else if let Some(d) = args.date {
335        cmd::trade_ext::run_acc_cash_flow(
336            gateway,
337            acc_id,
338            &d,
339            &args.env,
340            &args.market,
341            args.direction,
342            output,
343        )
344        .await
345    } else {
346        anyhow::bail!("需传 --date <YYYY-MM-DD> 或 --date-range <FROM..TO>")
347    }
348}