Skip to main content

futucli/cli/
dispatch.rs

1use anyhow::Result;
2
3use super::Command;
4use crate::cmd;
5use crate::common::parse_symbol_csv;
6use crate::output::OutputFormat;
7
8mod qot;
9mod tier_m;
10
11/// 分发一条已解析的 `Command` 到对应 handler。
12///
13/// 供 main 入口与 REPL 共用。REPL 会禁用其中不适合在子 shell 内跑的命令
14/// (例如再进入 `Repl`),做法:在调用前 short-circuit。
15pub async fn dispatch(gateway: &str, output: OutputFormat, command: Command) -> Result<()> {
16    match command {
17        Command::Ping => cmd::ping::run(gateway, output).await,
18        Command::Quote(args) => qot::dispatch_quote(gateway, output, args).await,
19        Command::Snapshot(args) => qot::dispatch_snapshot(gateway, output, args).await,
20        Command::Sub(args) => qot::dispatch_sub(gateway, output, args).await,
21        Command::Kline(args) => qot::dispatch_kline(gateway, output, args).await,
22        Command::Orderbook(args) => qot::dispatch_orderbook(gateway, output, args).await,
23        Command::Ticker(args) => qot::dispatch_ticker(gateway, output, args).await,
24        Command::Rt(args) => qot::dispatch_rt(gateway, output, args).await,
25        Command::Static(args) => qot::dispatch_static(gateway, output, args).await,
26        Command::Broker(args) => qot::dispatch_broker(gateway, output, args).await,
27        Command::PlateList(args) => qot::dispatch_plate_list(gateway, output, args).await,
28        Command::PlateStocks(args) => qot::dispatch_plate_stocks(gateway, output, args).await,
29        Command::Account {
30            market,
31            security_firm,
32            all,
33        } => {
34            cmd::account::list_accounts(
35                gateway,
36                output,
37                market.as_deref(),
38                security_firm.as_deref(),
39                all,
40            )
41            .await
42        }
43        Command::Funds(args) => {
44            let acc_id = cmd::account::resolve_account_locator(
45                gateway,
46                args.acc_id,
47                args.card_num.as_deref(),
48                "funds",
49            )
50            .await?;
51            cmd::account::funds(
52                gateway,
53                &args.env,
54                acc_id,
55                args.market.as_deref(),
56                args.currency.as_deref(),
57                output,
58            )
59            .await
60        }
61        Command::Position(args) => {
62            let acc_id = cmd::account::resolve_account_locator(
63                gateway,
64                args.acc_id,
65                args.card_num.as_deref(),
66                "positions",
67            )
68            .await?;
69            cmd::account::positions(gateway, &args.env, acc_id, &args.market, output).await
70        }
71        Command::Order(args) => {
72            let acc_id = cmd::account::resolve_account_locator(
73                gateway,
74                args.acc_id,
75                args.card_num.as_deref(),
76                "orders",
77            )
78            .await?;
79            cmd::account::orders(gateway, &args.env, acc_id, &args.market, output).await
80        }
81        Command::Deal(args) => {
82            let acc_id = cmd::account::resolve_account_locator(
83                gateway,
84                args.acc_id,
85                args.card_num.as_deref(),
86                "deals",
87            )
88            .await?;
89            cmd::account::deals(gateway, &args.env, acc_id, &args.market, output).await
90        }
91        Command::UnlockTrade {
92            lock,
93            from_stdin,
94            otp,
95            security_firm,
96            acc_ids,
97        } => {
98            cmd::unlock::run(
99                gateway,
100                lock,
101                from_stdin,
102                otp,
103                security_firm,
104                acc_ids,
105                output,
106            )
107            .await
108        }
109        Command::SetTradePwd {
110            account,
111            from_stdin,
112        } => cmd::unlock::set_trade_pwd(&account, from_stdin).await,
113        Command::ClearTradePwd { account } => cmd::unlock::clear_trade_pwd(&account).await,
114        Command::SetLoginPwd {
115            account,
116            from_stdin,
117        } => cmd::unlock::set_login_pwd(&account, from_stdin).await,
118        Command::ClearLoginPwd { account } => cmd::unlock::clear_login_pwd(&account).await,
119        Command::Repl => {
120            // REPL 由顶层(main)或用户显式切换,不从 dispatch 再次进入,
121            // 这样避免 `async fn` 递归类型无限膨胀。
122            anyhow::bail!(
123                "cannot enter REPL from this context (already nested / not a top-level invocation)"
124            )
125        }
126        Command::GenKey(args) => {
127            cmd::gen_key::run(cmd::gen_key::GenKeyCommand {
128                id: args.id,
129                scopes: args.scopes,
130                keys_file: args.keys_file,
131                expires: args.expires,
132                note: args.note,
133                allowed_markets: args.allowed_markets,
134                allowed_symbols: args.allowed_symbols,
135                max_order_value: args.max_order_value,
136                max_daily_value: args.max_daily_value,
137                hours_window: args.hours_window,
138                max_orders_per_minute: args.max_orders_per_minute,
139                allowed_trd_sides: args.allowed_trd_sides,
140                allowed_acc_ids: args.allowed_acc_ids,
141                allowed_card_nums: args.allowed_card_nums,
142                bind_this_machine: args.bind_this_machine,
143                bind_machines: args.bind_machines,
144            })
145            .await
146        }
147        Command::BindKey {
148            id,
149            keys_file,
150            this_machine,
151            machines,
152            replace,
153            clear,
154            freeze,
155        } => {
156            cmd::bind_key::run(cmd::bind_key::BindKeyCommand {
157                id,
158                keys_file,
159                this_machine,
160                machines,
161                replace,
162                clear,
163                freeze,
164            })
165            .await
166        }
167        Command::MachineId { for_key } => cmd::machine::run(for_key).await,
168        Command::ListKeys { keys_file, json } => cmd::keys::list(keys_file, json).await,
169        Command::RevokeKey { id, keys_file, yes } => cmd::keys::revoke(id, keys_file, yes).await,
170
171        // ===== v1.4.25: 交易扩展命令 =====
172        Command::PlaceOrder(args) => {
173            let acc_id = cmd::account::resolve_account_locator(
174                gateway,
175                args.acc_id,
176                args.card_num.as_deref(),
177                "place-order",
178            )
179            .await?;
180            cmd::trade_ext::run_place_order(cmd::trade_ext::PlaceOrderCommand {
181                gateway,
182                env: &args.env,
183                acc_id,
184                market: &args.market,
185                side: &args.side,
186                order_type: &args.order_type,
187                code: &args.code,
188                qty: args.qty,
189                price: args.price,
190                confirm: args.confirm,
191                idempotency_key: args.idempotency_key,
192                stop_price: args.stop_price,
193                trail_type: args.trail_type,
194                trail_value: args.trail_value,
195                trail_spread: args.trail_spread,
196                output,
197            })
198            .await
199        }
200        Command::ModifyOrder(args) => {
201            let acc_id = cmd::account::resolve_account_locator(
202                gateway,
203                args.acc_id,
204                args.card_num.as_deref(),
205                "modify-order",
206            )
207            .await?;
208            cmd::trade_ext::run_modify_order(cmd::trade_ext::ModifyOrderCommand {
209                gateway,
210                env: &args.env,
211                acc_id,
212                market: &args.market,
213                order_id: args.order_id,
214                op: &args.op,
215                qty: args.qty,
216                price: args.price,
217                confirm: args.confirm,
218                idempotency_key: args.idempotency_key,
219                output,
220            })
221            .await
222        }
223        Command::CancelOrder(args) => {
224            let acc_id = cmd::account::resolve_account_locator(
225                gateway,
226                args.acc_id,
227                args.card_num.as_deref(),
228                "cancel-order",
229            )
230            .await?;
231            cmd::trade_ext::run_cancel_order(
232                gateway,
233                &args.env,
234                acc_id,
235                &args.market,
236                args.order_id,
237                args.confirm,
238                args.idempotency_key,
239                output,
240            )
241            .await
242        }
243        Command::ReconfirmOrder(args) => {
244            let acc_id = cmd::account::resolve_account_locator(
245                gateway,
246                args.acc_id,
247                args.card_num.as_deref(),
248                "reconfirm-order",
249            )
250            .await?;
251            cmd::trade_ext::run_reconfirm_order(
252                gateway,
253                &args.env,
254                acc_id,
255                &args.market,
256                args.order_id,
257                args.reason,
258                args.confirm,
259                output,
260            )
261            .await
262        }
263        Command::HistoryOrders(args) => {
264            let acc_id = cmd::account::resolve_account_locator(
265                gateway,
266                args.acc_id,
267                args.card_num.as_deref(),
268                "history-orders",
269            )
270            .await?;
271            let code_list = args
272                .codes
273                .map(|s| {
274                    s.split(',')
275                        .map(|x| x.trim().to_string())
276                        .filter(|x| !x.is_empty())
277                        .collect()
278                })
279                .unwrap_or_default();
280            cmd::trade_ext::run_history_orders(cmd::trade_ext::HistoryOrdersCommand {
281                gateway,
282                env: &args.env,
283                acc_id,
284                market: &args.market,
285                codes: code_list,
286                begin: args.begin,
287                end: args.end,
288                output,
289            })
290            .await
291        }
292        Command::HistoryDeals(args) => {
293            let acc_id = cmd::account::resolve_account_locator(
294                gateway,
295                args.acc_id,
296                args.card_num.as_deref(),
297                "history-deals",
298            )
299            .await?;
300            let code_list = args
301                .codes
302                .map(|s| {
303                    s.split(',')
304                        .map(|x| x.trim().to_string())
305                        .filter(|x| !x.is_empty())
306                        .collect()
307                })
308                .unwrap_or_default();
309            cmd::trade_ext::run_history_deals(cmd::trade_ext::HistoryDealsCommand {
310                gateway,
311                env: &args.env,
312                acc_id,
313                market: &args.market,
314                codes: code_list,
315                begin: args.begin,
316                end: args.end,
317                output,
318            })
319            .await
320        }
321        Command::MaxQtys(args) => {
322            let acc_id = cmd::account::resolve_account_locator(
323                gateway,
324                args.acc_id,
325                args.card_num.as_deref(),
326                "max-trd-qtys",
327            )
328            .await?;
329            cmd::trade_ext::run_max_qtys(cmd::trade_ext::MaxQtysCommand {
330                gateway,
331                env: &args.env,
332                acc_id,
333                market: &args.market,
334                order_type: &args.order_type,
335                code: &args.code,
336                price: args.price,
337                output,
338            })
339            .await
340        }
341        // v1.4.31
342        Command::MarginRatio(args) => {
343            let acc_id = cmd::account::resolve_account_locator(
344                gateway,
345                args.acc_id,
346                args.card_num.as_deref(),
347                "margin-ratio",
348            )
349            .await?;
350            let symbols = args.symbols.or(args.symbols_arg).ok_or_else(|| {
351                anyhow::anyhow!("margin-ratio: 需要位置参数 <SYMBOLS> 或 --code / --symbols")
352            })?;
353            let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
354            cmd::trade_ext::run_margin_ratio(
355                gateway,
356                &args.env,
357                acc_id,
358                &args.market,
359                &syms,
360                output,
361            )
362            .await
363        }
364        Command::OrderFee(args) => {
365            let acc_id = cmd::account::resolve_account_locator(
366                gateway,
367                args.acc_id,
368                args.card_num.as_deref(),
369                "order-fee",
370            )
371            .await?;
372            let ids: Vec<String> = args
373                .order_ids
374                .split(',')
375                .map(|s| s.trim().to_string())
376                .collect();
377            cmd::trade_ext::run_order_fee(gateway, &args.env, acc_id, &args.market, &ids, output)
378                .await
379        }
380        Command::CapitalFlow {
381            symbol,
382            period_type,
383            begin,
384            end,
385        } => {
386            cmd::analysis::run_capital_flow(
387                gateway,
388                &symbol,
389                period_type,
390                begin.as_deref(),
391                end.as_deref(),
392                output,
393            )
394            .await
395        }
396        Command::CapitalDistribution { symbol } => {
397            cmd::analysis::run_capital_distribution(gateway, &symbol, output).await
398        }
399        Command::MarketState { symbols } => {
400            // v1.4.106 codex 0641 F6 (P3): 整体 reject 空 token (而非
401            // silent fallback 把 "" 当 symbol 发出去).
402            let list = parse_symbol_csv(&symbols)?;
403            cmd::analysis::run_market_state(gateway, &list, output).await
404        }
405        Command::OwnerPlate { symbols } => {
406            // v1.4.106 codex 0641 F6 (P3).
407            let list = parse_symbol_csv(&symbols)?;
408            cmd::analysis::run_owner_plate(gateway, &list, output).await
409        }
410        Command::OptionChain {
411            owner,
412            owner_arg,
413            begin,
414            end,
415            option_type,
416            delta_min,
417            delta_max,
418            iv_min,
419            iv_max,
420            oi_min,
421            oi_max,
422            gamma_min,
423            gamma_max,
424            vega_min,
425            vega_max,
426            theta_min,
427            theta_max,
428        } => {
429            let owner = owner.or(owner_arg).ok_or_else(|| {
430                anyhow::anyhow!("option-chain: 需要位置参数 <OWNER> 或 --owner / --code")
431            })?;
432            cmd::analysis::run_option_chain(
433                gateway,
434                &owner,
435                &begin,
436                &end,
437                &option_type,
438                cmd::analysis::OptionChainGreekFilterArgs {
439                    delta_min,
440                    delta_max,
441                    iv_min,
442                    iv_max,
443                    oi_min,
444                    oi_max,
445                    gamma_min,
446                    gamma_max,
447                    vega_min,
448                    vega_max,
449                    theta_min,
450                    theta_max,
451                },
452                output,
453            )
454            .await
455        }
456
457        // v1.4.30
458        Command::TradingDays { market, begin, end } => {
459            cmd::analysis::run_trading_days(gateway, &market, &begin, &end, output).await
460        }
461        Command::Rehab { symbol } => cmd::analysis::run_rehab(gateway, &symbol, output).await,
462        Command::Suspend {
463            symbols,
464            symbols_arg,
465            begin,
466            end,
467        } => {
468            let symbols = symbols.or(symbols_arg).ok_or_else(|| {
469                anyhow::anyhow!("suspend: 需要位置参数 <SYMBOLS> 或 --code / --symbols")
470            })?;
471            // v1.4.106 codex 0641 F6 (P3).
472            let syms = parse_symbol_csv(&symbols)?;
473            cmd::analysis::run_suspend(gateway, &syms, &begin, &end, output).await
474        }
475        Command::UserSecurity { group, group_arg } => {
476            let group = group
477                .or(group_arg)
478                .ok_or_else(|| anyhow::anyhow!("user-security: 需要位置参数 <GROUP> 或 --group"))?;
479            cmd::analysis::run_user_security(gateway, &group, output).await
480        }
481        Command::UserSecurityGroups { group_type } => {
482            cmd::analysis::run_user_security_groups(gateway, group_type, output).await
483        }
484        Command::Warrant { owner, begin, num } => {
485            cmd::analysis::run_warrant(gateway, owner.as_deref(), begin, num, output).await
486        }
487        Command::IpoList { market } => cmd::analysis::run_ipo_list(gateway, &market, output).await,
488        Command::FutureInfo { symbols } => {
489            // v1.4.106 codex 0641 F6 (P3).
490            let syms = parse_symbol_csv(&symbols)?;
491            cmd::analysis::run_future_info(gateway, &syms, output).await
492        }
493        Command::StockFilter { market, begin, num } => {
494            cmd::analysis::run_stock_filter(gateway, &market, begin, num, output).await
495        }
496        Command::CancelAllOrder {
497            acc_id,
498            card_num,
499            env,
500            market,
501            confirm,
502        } => {
503            let acc_id = cmd::account::resolve_account_locator(
504                gateway,
505                acc_id,
506                card_num.as_deref(),
507                "cancel-all-order",
508            )
509            .await?;
510            cmd::trade_ext::run_cancel_all_order(
511                gateway,
512                acc_id,
513                &env,
514                market.as_deref(),
515                confirm,
516                output,
517            )
518            .await
519        }
520        Command::GlobalState => cmd::sys::run_global_state(gateway, output).await,
521        Command::UserInfo => cmd::sys::run_user_info(gateway, output).await,
522        Command::QuoteRights { refresh } => {
523            cmd::sys::run_quote_rights(gateway, refresh, output).await
524        }
525        Command::DelayStatistics => cmd::sys::run_delay_statistics(gateway, output).await,
526        Command::TokenState => cmd::sys::run_token_state(gateway, output).await,
527        Command::RiskFreeRate => cmd::sys::run_risk_free_rate(gateway, output).await,
528        Command::SpreadTable => cmd::sys::run_spread_table(gateway, output).await,
529        Command::TickerStatistic {
530            symbol_pos,
531            symbol,
532            ticker_type,
533            stat_type,
534        } => {
535            // v1.4.102 A3 alias: positional 优先, 否则 --symbol named.
536            let sym = symbol_pos.or(symbol).ok_or_else(|| {
537                anyhow::anyhow!("ticker-statistic: SYMBOL or --symbol required (e.g. HK.00700)")
538            })?;
539            cmd::sys::run_ticker_statistic(gateway, &sym, ticker_type, stat_type, output).await
540        }
541        // v1.4.106 codex 0500 ζ23-redo: TickerStatistic Detail (cmd 6366)
542        Command::TickerStatisticDetail {
543            symbol_pos,
544            symbol,
545            ticker_type,
546            ticker_time,
547            select_num,
548            data_from,
549            data_max_count,
550            stat_type,
551        } => {
552            let sym = symbol_pos.or(symbol).ok_or_else(|| {
553                anyhow::anyhow!(
554                    "ticker-statistic-detail: SYMBOL or --symbol required (e.g. HK.00700)"
555                )
556            })?;
557            cmd::sys::run_ticker_statistic_detail(cmd::sys::TickerStatisticDetailCommand {
558                gateway,
559                symbol: &sym,
560                ticker_type,
561                ticker_time,
562                select_num,
563                data_from,
564                data_max_count,
565                stat_type,
566                format: output,
567            })
568            .await
569        }
570
571        // v1.4.30 P2(100% 覆盖)
572        Command::QuerySubscription { all_conn } => {
573            cmd::sys::run_query_subscription(gateway, all_conn, output).await
574        }
575        Command::UsedQuota => cmd::sys::run_used_quota(gateway, output).await,
576        Command::Unsubscribe {
577            symbols,
578            sub_types,
579            all,
580        } => {
581            let syms: Vec<String> = if symbols.trim().is_empty() {
582                vec![]
583            } else {
584                symbols.split(',').map(|s| s.trim().to_string()).collect()
585            };
586            let types: Vec<i32> = if sub_types.trim().is_empty() {
587                vec![]
588            } else {
589                sub_types
590                    .split(',')
591                    .map(|s| s.trim().parse::<i32>())
592                    .collect::<std::result::Result<Vec<_>, _>>()
593                    .map_err(|e| anyhow::anyhow!("invalid sub-type: {e}"))?
594            };
595            cmd::sys::run_unsubscribe(gateway, &syms, &types, all, output).await
596        }
597        Command::HistoryKlQuota { detail } => {
598            cmd::analysis::run_history_kl_quota(gateway, detail, output).await
599        }
600        Command::HoldingChange {
601            symbol,
602            category,
603            begin,
604            end,
605        } => {
606            cmd::analysis::run_holding_change(
607                gateway,
608                &symbol,
609                category,
610                begin.as_deref(),
611                end.as_deref(),
612                output,
613            )
614            .await
615        }
616        Command::ModifyUserSecurity { group, op, symbols } => {
617            let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
618            cmd::analysis::run_modify_user_security(gateway, &group, op, &syms, output).await
619        }
620        Command::CodeChange { symbols } => {
621            let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
622            cmd::analysis::run_code_change(gateway, &syms, output).await
623        }
624        Command::SetPriceReminder {
625            symbol,
626            op,
627            key,
628            r#type,
629            freq,
630            value,
631            note,
632            session,
633        } => {
634            cmd::analysis::run_set_price_reminder(cmd::analysis::SetPriceReminderCommand {
635                gateway,
636                symbol: &symbol,
637                op,
638                key,
639                reminder_type: r#type,
640                freq,
641                value,
642                note: note.as_deref(),
643                reminder_session_list: &session,
644            })
645            .await
646        }
647        Command::PriceReminder { symbol, market } => {
648            cmd::analysis::run_get_price_reminder(gateway, symbol.as_deref(), market, output).await
649        }
650        Command::OptionExpirationDate {
651            owner,
652            owner_arg,
653            index_type,
654        } => {
655            let owner = owner.or(owner_arg).ok_or_else(|| {
656                anyhow::anyhow!("option-expiration-date: 需要位置参数 <OWNER> 或 --owner")
657            })?;
658            cmd::analysis::run_option_expiration_date(gateway, &owner, index_type, output).await
659        }
660        Command::SubAccPush { acc_ids } => {
661            let ids: Vec<u64> = acc_ids
662                .split(',')
663                .map(|s| s.trim().parse::<u64>())
664                .collect::<std::result::Result<Vec<_>, _>>()
665                .map_err(|e| anyhow::anyhow!("invalid acc id: {e}"))?;
666            cmd::trade_ext::run_sub_acc_push(gateway, &ids, output).await
667        }
668        Command::AccCashFlow(args) => {
669            let acc_id = cmd::account::resolve_account_locator(
670                gateway,
671                args.acc_id.or(args.acc_id_arg),
672                args.card_num.as_deref(),
673                "acc-cash-flow",
674            )
675            .await?;
676            // date / date_range 通过 #[arg(conflicts_with)] 互斥
677            if let Some(range) = args.date_range {
678                // 解析 "YYYY-MM-DD..YYYY-MM-DD"
679                let (from_s, to_s) = range.split_once("..").ok_or_else(|| {
680                    anyhow::anyhow!("--date-range 格式应为 `YYYY-MM-DD..YYYY-MM-DD`,当前:{range}")
681                })?;
682                let from = chrono::NaiveDate::parse_from_str(from_s, "%Y-%m-%d")
683                    .map_err(|e| anyhow::anyhow!("start date parse: {e}"))?;
684                let to = chrono::NaiveDate::parse_from_str(to_s, "%Y-%m-%d")
685                    .map_err(|e| anyhow::anyhow!("end date parse: {e}"))?;
686                cmd::trade_ext::run_acc_cash_flow_range(cmd::trade_ext::AccCashFlowRangeCommand {
687                    gateway,
688                    acc_id,
689                    date_from: from,
690                    date_to: to,
691                    env: &args.env,
692                    market: &args.market,
693                    direction: args.direction,
694                })
695                .await
696            } else if let Some(d) = args.date {
697                cmd::trade_ext::run_acc_cash_flow(
698                    gateway,
699                    acc_id,
700                    &d,
701                    &args.env,
702                    &args.market,
703                    args.direction,
704                    output,
705                )
706                .await
707            } else {
708                anyhow::bail!("需传 --date <YYYY-MM-DD> 或 --date-range <FROM..TO>")
709            }
710        }
711        Command::DaemonStatus {
712            rest_url,
713            rest_port,
714            api_key,
715        } => {
716            // v1.4.52 BUG-9: --rest-port 转成 http://127.0.0.1:<port> 兼容 REST/MCP 风格
717            let effective_url =
718                rest_url.or_else(|| rest_port.map(|p| format!("http://127.0.0.1:{p}")));
719            cmd::daemon::run_status(effective_url.as_deref(), api_key.as_deref(), output).await
720        }
721        Command::DaemonShutdown {
722            rest_url,
723            rest_port,
724            api_key,
725        } => {
726            // v1.4.106 codex 0554 F5 [P3]: --rest-port 与 daemon-status 对齐.
727            let effective_url =
728                rest_url.or_else(|| rest_port.map(|p| format!("http://127.0.0.1:{p}")));
729            cmd::daemon::run_shutdown(effective_url.as_deref(), api_key.as_deref()).await
730        }
731        Command::DaemonReload {
732            rest_url,
733            rest_port,
734            api_key,
735        } => {
736            // v1.4.106 codex 0554 F5 [P3]: --rest-port 与 daemon-status 对齐.
737            let effective_url =
738                rest_url.or_else(|| rest_port.map(|p| format!("http://127.0.0.1:{p}")));
739            cmd::daemon::run_reload(effective_url.as_deref(), api_key.as_deref()).await
740        }
741
742        // ====================================================================
743        // v1.4.94 / v1.4.95 Tier M (mobile-driven extensions, 11 endpoint)
744        // ====================================================================
745        Command::CashLog(args) => tier_m::dispatch_cash_log(gateway, output, args).await,
746        Command::CashDetail(args) => tier_m::dispatch_cash_detail(gateway, output, args).await,
747        Command::BizGroup(args) => tier_m::dispatch_biz_group(gateway, output, args).await,
748        Command::MarginInfo(args) => tier_m::dispatch_margin_info(gateway, output, args).await,
749        Command::AccountFlag(args) => tier_m::dispatch_account_flag(gateway, output, args).await,
750        Command::BondTotalAsset(args) => {
751            tier_m::dispatch_bond_total_asset(gateway, output, args).await
752        }
753        Command::BondSingleAsset(args) => {
754            tier_m::dispatch_bond_single_asset(gateway, output, args).await
755        }
756        Command::BondPositionList(args) => {
757            tier_m::dispatch_bond_position_list(gateway, output, args).await
758        }
759        Command::BondAnswerState(args) => {
760            tier_m::dispatch_bond_answer_state(gateway, output, args).await
761        }
762        Command::BondTradeReminder(args) => {
763            tier_m::dispatch_bond_trade_reminder(gateway, output, args).await
764        }
765    }
766}