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 account;
9mod daemon;
10mod qot;
11mod tier_m;
12mod trade_read;
13mod trade_write;
14
15/// 分发一条已解析的 `Command` 到对应 handler。
16///
17/// 供 main 入口与 REPL 共用。REPL 会禁用其中不适合在子 shell 内跑的命令
18/// (例如再进入 `Repl`),做法:在调用前 short-circuit。
19pub async fn dispatch(gateway: &str, output: OutputFormat, command: Command) -> Result<()> {
20    match command {
21        Command::Ping => cmd::ping::run(gateway, output).await,
22        Command::Commands(args) => {
23            cmd::command_catalog::run(args.group.as_deref(), args.search.as_deref(), output)
24        }
25        Command::Version(args) => {
26            cmd::version_check::run_version(
27                args.check,
28                args.url.as_deref(),
29                args.timeout_ms,
30                output,
31            )
32            .await
33        }
34        Command::LangPack(args) => cmd::lang_pack::run(args.command, output).await,
35        Command::Quote(args) => qot::dispatch_quote(gateway, output, args).await,
36        Command::Snapshot(args) => qot::dispatch_snapshot(gateway, output, args).await,
37        Command::Sub(args) => qot::dispatch_sub(gateway, output, args).await,
38        Command::Kline(args) => qot::dispatch_kline(gateway, output, args).await,
39        Command::PushSubscriberInfo(args) => daemon::dispatch_push_sub_info(args, output).await,
40        Command::Orderbook(args) => qot::dispatch_orderbook(gateway, output, args).await,
41        Command::Ticker(args) => qot::dispatch_ticker(gateway, output, args).await,
42        Command::Rt(args) => qot::dispatch_rt(gateway, output, args).await,
43        Command::Static(args) => qot::dispatch_static(gateway, output, args).await,
44        Command::StaticStatus(args) => {
45            let rest_url = args
46                .rest_url
47                .or_else(|| args.rest_port.map(|p| format!("http://127.0.0.1:{p}")));
48            cmd::static_diag::run_static_status(
49                rest_url.as_deref(),
50                args.api_key.as_deref(),
51                output,
52            )
53            .await
54        }
55        Command::StaticWarmup(args) => cmd::static_info::run(gateway, &args.symbols, output).await,
56        Command::Broker(args) => qot::dispatch_broker(gateway, output, args).await,
57        Command::PlateList(args) => qot::dispatch_plate_list(gateway, output, args).await,
58        Command::PlateStocks(args) => qot::dispatch_plate_stocks(gateway, output, args).await,
59        Command::Reference(args) => qot::dispatch_reference(gateway, output, args).await,
60        Command::OptionQuote(args) => qot::dispatch_option_quote(gateway, output, args).await,
61        Command::OptionStrategy(args) => qot::dispatch_option_strategy(gateway, output, args).await,
62        Command::OptionStrategyAnalysis(args) => {
63            qot::dispatch_option_strategy_analysis(gateway, output, args).await
64        }
65        Command::OptionStrategySpread(args) => {
66            qot::dispatch_option_strategy_spread(gateway, output, args).await
67        }
68        Command::EarningsCalendar(args) => {
69            qot::dispatch_earnings_calendar(gateway, output, args).await
70        }
71        Command::MacroIndicatorList(args) => {
72            qot::dispatch_macro_indicator_list(gateway, output, args).await
73        }
74        Command::MacroIndicatorHistory(args) => {
75            qot::dispatch_macro_indicator_history(gateway, output, args).await
76        }
77        Command::FedWatchTargetRate(args) => {
78            qot::dispatch_fed_watch_target_rate(gateway, output, args).await
79        }
80        Command::FedWatchDotPlot(args) => {
81            qot::dispatch_fed_watch_dot_plot(gateway, output, args).await
82        }
83        Command::EarningsBeatRank(args) => {
84            qot::dispatch_earnings_beat_rank(gateway, output, args).await
85        }
86        Command::DividendRank(args) => qot::dispatch_dividend_rank(gateway, output, args).await,
87        Command::DividendCalendar(args) => {
88            qot::dispatch_dividend_calendar(gateway, output, args).await
89        }
90        Command::EconomicCalendar(args) => {
91            qot::dispatch_economic_calendar(gateway, output, args).await
92        }
93        Command::UsPreMarketRank(args) => {
94            qot::dispatch_us_pre_market_rank(gateway, output, args).await
95        }
96        Command::UsAfterHoursRank(args) => {
97            qot::dispatch_us_after_hours_rank(gateway, output, args).await
98        }
99        Command::UsOvernightRank(args) => {
100            qot::dispatch_us_overnight_rank(gateway, output, args).await
101        }
102        Command::TopMoversRank(args) => qot::dispatch_top_movers_rank(gateway, output, args).await,
103        Command::HotList(args) => qot::dispatch_hot_list(gateway, output, args).await,
104        Command::ShortSellingRank(args) => {
105            qot::dispatch_short_selling_rank(gateway, output, args).await
106        }
107        Command::PeriodChangeRank(args) => {
108            qot::dispatch_period_change_rank(gateway, output, args).await
109        }
110        Command::HighDividendSoeRank(args) => {
111            qot::dispatch_high_dividend_soe_rank(gateway, output, args).await
112        }
113        Command::InstitutionList(args) => {
114            qot::dispatch_institution_list(gateway, output, args).await
115        }
116        Command::InstitutionProfile(args) => {
117            qot::dispatch_institution_profile(gateway, output, args).await
118        }
119        Command::InstitutionDistribution(args) => {
120            qot::dispatch_institution_distribution(gateway, output, args).await
121        }
122        Command::InstitutionHoldingChange(args) => {
123            qot::dispatch_institution_holding_change(gateway, output, args).await
124        }
125        Command::InstitutionHoldingList(args) => {
126            qot::dispatch_institution_holding_list(gateway, output, args).await
127        }
128        Command::ArkFundHolding(args) => {
129            qot::dispatch_ark_fund_holding(gateway, output, args).await
130        }
131        Command::ArkStockDynamic(args) => {
132            qot::dispatch_ark_stock_dynamic(gateway, output, args).await
133        }
134        Command::ArkActiveTransaction(args) => {
135            qot::dispatch_ark_active_transaction(gateway, output, args).await
136        }
137        Command::RatingChange(args) => qot::dispatch_rating_change(gateway, output, args).await,
138        Command::IndustrialChainList(args) => {
139            qot::dispatch_industrial_chain_list(gateway, output, args).await
140        }
141        Command::IndustrialChainDetail(args) => {
142            qot::dispatch_industrial_chain_detail(gateway, output, args).await
143        }
144        Command::IndustrialChainByPlate(args) => {
145            qot::dispatch_industrial_chain_by_plate(gateway, output, args).await
146        }
147        Command::IndustrialPlateInfo(args) => {
148            qot::dispatch_industrial_plate_info(gateway, output, args).await
149        }
150        Command::IndustrialPlateStock(args) => {
151            qot::dispatch_industrial_plate_stock(gateway, output, args).await
152        }
153        Command::HeatMapData(args) => qot::dispatch_heat_map_data(gateway, output, args).await,
154        Command::RiseFallDistribution(args) => {
155            qot::dispatch_rise_fall_distribution(gateway, output, args).await
156        }
157        Command::Account(args) => account::dispatch_account(gateway, output, args).await,
158        Command::Funds(args) => trade_read::dispatch_funds(gateway, output, args).await,
159        Command::Position(args) => trade_read::dispatch_position(gateway, output, args).await,
160        Command::Order(args) => trade_read::dispatch_order(gateway, output, args).await,
161        Command::Deal(args) => trade_read::dispatch_deal(gateway, output, args).await,
162        Command::ComboMaxTrdQtys(args) => {
163            trade_read::dispatch_combo_max_trd_qtys(gateway, output, args).await
164        }
165        Command::UnlockTrade(args) => {
166            cmd::unlock::run(
167                gateway,
168                args.lock,
169                args.from_stdin,
170                args.trade_pwd_account.as_deref(),
171                args.otp,
172                args.security_firm,
173                args.acc_ids,
174                output,
175            )
176            .await
177        }
178        Command::SetTradePwd(args) => {
179            cmd::unlock::set_trade_pwd(&args.account, args.from_stdin).await
180        }
181        Command::ClearTradePwd(args) => cmd::unlock::clear_trade_pwd(&args.account).await,
182        Command::SetLoginPwd(args) => {
183            cmd::unlock::set_login_pwd(&args.account, args.from_stdin).await
184        }
185        Command::ClearLoginPwd(args) => cmd::unlock::clear_login_pwd(&args.account).await,
186        Command::Repl => {
187            // REPL 由顶层(main)或用户显式切换,不从 dispatch 再次进入,
188            // 这样避免 `async fn` 递归类型无限膨胀。
189            anyhow::bail!(
190                "cannot enter REPL from this context (already nested / not a top-level invocation)"
191            )
192        }
193        Command::GenKey(args) => {
194            cmd::gen_key::run(cmd::gen_key::GenKeyCommand {
195                id: args.id,
196                scopes: args.scopes,
197                keys_file: args.keys_file,
198                expires: args.expires,
199                note: args.note,
200                allowed_markets: args.allowed_markets,
201                allowed_symbols: args.allowed_symbols,
202                max_order_value: args.max_order_value,
203                max_daily_value: args.max_daily_value,
204                hours_window: args.hours_window,
205                max_orders_per_minute: args.max_orders_per_minute,
206                allowed_trd_sides: args.allowed_trd_sides,
207                allowed_acc_ids: args.allowed_acc_ids,
208                allowed_card_nums: args.allowed_card_nums,
209                bind_this_machine: args.bind_this_machine,
210                bind_machines: args.bind_machines,
211            })
212            .await
213        }
214        Command::BindKey(args) => {
215            cmd::bind_key::run(cmd::bind_key::BindKeyCommand {
216                id: args.id,
217                keys_file: args.keys_file,
218                this_machine: args.this_machine,
219                machines: args.machines,
220                replace: args.replace,
221                clear: args.clear,
222                freeze: args.freeze,
223            })
224            .await
225        }
226        Command::MachineId(args) => cmd::machine::run(args.for_key).await,
227        Command::ListKeys(args) => cmd::keys::list(args.keys_file, args.json).await,
228        Command::RevokeKey(args) => cmd::keys::revoke(args.id, args.keys_file, args.yes).await,
229
230        // ===== v1.4.25: 交易扩展命令 =====
231        Command::PlaceOrder(args) => trade_write::dispatch_place_order(gateway, output, args).await,
232        Command::ModifyOrder(args) => {
233            trade_write::dispatch_modify_order(gateway, output, args).await
234        }
235        Command::CancelOrder(args) => {
236            trade_write::dispatch_cancel_order(gateway, output, args).await
237        }
238        Command::ReconfirmOrder(args) => {
239            trade_write::dispatch_reconfirm_order(gateway, output, args).await
240        }
241        Command::HistoryOrders(args) => {
242            trade_write::dispatch_history_orders(gateway, output, args).await
243        }
244        Command::HistoryDeals(args) => {
245            trade_write::dispatch_history_deals(gateway, output, args).await
246        }
247        Command::MaxQtys(args) => trade_write::dispatch_max_qtys(gateway, output, args).await,
248        Command::TradeCheck(args) => trade_read::dispatch_trade_check(gateway, output, args).await,
249        Command::ComboOrder(args) => {
250            cmd::proto_json::run_place_combo_order(
251                gateway,
252                &args.c2s_json,
253                args.confirm,
254                args.idempotency_key,
255                output,
256            )
257            .await
258        }
259        // v1.4.31
260        Command::MarginRatio(args) => {
261            trade_write::dispatch_margin_ratio(gateway, output, args).await
262        }
263        Command::OrderFee(args) => trade_write::dispatch_order_fee(gateway, output, args).await,
264        Command::CapitalFlow(args) => qot::dispatch_capital_flow(gateway, output, args).await,
265        Command::CapitalDistribution { symbol } => {
266            cmd::analysis::run_capital_distribution(gateway, &symbol, output).await
267        }
268        Command::CompanyProfile(args) => qot::dispatch_company_profile(gateway, output, args).await,
269        Command::CompanyExecutives(args) => {
270            qot::dispatch_company_executives(gateway, output, args).await
271        }
272        Command::CompanyExecutiveBackground(args) => {
273            qot::dispatch_company_executive_background(gateway, output, args).await
274        }
275        Command::CompanyOperationalEfficiency(args) => {
276            qot::dispatch_company_operational_efficiency(gateway, output, args).await
277        }
278        Command::FinancialsEarningsPriceMove(args) => {
279            qot::dispatch_financials_earnings_price_move(gateway, output, args).await
280        }
281        Command::FinancialsEarningsPriceHistory(args) => {
282            qot::dispatch_financials_earnings_price_history(gateway, output, args).await
283        }
284        Command::FinancialCalendar(args) => {
285            qot::dispatch_financial_calendar(gateway, output, args).await
286        }
287        Command::FinancialCalendarTarget(args) => {
288            qot::dispatch_financial_calendar_target(gateway, output, args).await
289        }
290        Command::FinancialsStatements(args) => {
291            qot::dispatch_financials_statements(gateway, output, args).await
292        }
293        Command::FinancialsRevenueBreakdown(args) => {
294            qot::dispatch_financials_revenue_breakdown(gateway, output, args).await
295        }
296        Command::ResearchAnalystConsensus(args) => {
297            qot::dispatch_research_analyst_consensus(gateway, output, args).await
298        }
299        Command::ResearchRatingSummary(args) => {
300            qot::dispatch_research_rating_summary(gateway, output, args).await
301        }
302        Command::ResearchMorningstarReport(args) => {
303            qot::dispatch_research_morningstar_report(gateway, output, args).await
304        }
305        Command::ValuationDetail(args) => {
306            qot::dispatch_valuation_detail(gateway, output, args).await
307        }
308        Command::ValuationPlateStockList(args) => {
309            qot::dispatch_valuation_plate_stock_list(gateway, output, args).await
310        }
311        Command::StockScreen(args) => qot::dispatch_stock_screen(gateway, output, args).await,
312        Command::OptionScreen(args) => qot::dispatch_option_screen(gateway, output, args).await,
313        Command::WarrantScreen(args) => qot::dispatch_warrant_screen(gateway, output, args).await,
314        Command::TechnicalUnusual(args) => {
315            qot::dispatch_technical_unusual(gateway, output, args).await
316        }
317        Command::FinancialUnusual(args) => {
318            qot::dispatch_financial_unusual(gateway, output, args).await
319        }
320        Command::DerivativeUnusual(args) => {
321            qot::dispatch_derivative_unusual(gateway, output, args).await
322        }
323        Command::CorporateActionsBuybacks(args) => {
324            qot::dispatch_corporate_actions_buybacks(gateway, output, args).await
325        }
326        Command::CorporateActionsDividends(args) => {
327            qot::dispatch_corporate_actions_dividends(gateway, output, args).await
328        }
329        Command::CorporateActionsStockSplits(args) => {
330            qot::dispatch_corporate_actions_stock_splits(gateway, output, args).await
331        }
332        Command::DailyShortVolume(args) => {
333            qot::dispatch_daily_short_volume(gateway, output, args).await
334        }
335        Command::ShortInterest(args) => qot::dispatch_short_interest(gateway, output, args).await,
336        Command::TopTenBuySellBrokers(args) => {
337            qot::dispatch_top_ten_buy_sell_brokers(gateway, output, args).await
338        }
339        Command::ShareholdersOverview(args) => {
340            qot::dispatch_shareholders_overview(gateway, output, args).await
341        }
342        Command::ShareholdersHoldingChanges(args) => {
343            qot::dispatch_shareholders_holding_changes(gateway, output, args).await
344        }
345        Command::ShareholdersHolderDetail(args) => {
346            qot::dispatch_shareholders_holder_detail(gateway, output, args).await
347        }
348        Command::ShareholdersInstitutional(args) => {
349            qot::dispatch_shareholders_institutional(gateway, output, args).await
350        }
351        Command::InsiderHolderList(args) => {
352            qot::dispatch_insider_holder_list(gateway, output, args).await
353        }
354        Command::InsiderTradeList(args) => {
355            qot::dispatch_insider_trade_list(gateway, output, args).await
356        }
357        Command::OptionVolatility(args) => {
358            qot::dispatch_option_volatility(gateway, output, args).await
359        }
360        Command::OptionExerciseProbability(args) => {
361            qot::dispatch_option_exercise_probability(gateway, output, args).await
362        }
363        Command::MarketState { symbols } => {
364            // v1.4.106 codex 0641 F6 (P3): 整体 reject 空 token (而非
365            // silent fallback 把 "" 当 symbol 发出去).
366            let list = parse_symbol_csv(&symbols)?;
367            cmd::analysis::run_market_state(gateway, &list, output).await
368        }
369        Command::OwnerPlate { symbols } => {
370            // v1.4.106 codex 0641 F6 (P3).
371            let list = parse_symbol_csv(&symbols)?;
372            cmd::analysis::run_owner_plate(gateway, &list, output).await
373        }
374        Command::OptionChain {
375            owner,
376            owner_arg,
377            begin,
378            end,
379            option_type,
380            delta_min,
381            delta_max,
382            iv_min,
383            iv_max,
384            oi_min,
385            oi_max,
386            gamma_min,
387            gamma_max,
388            vega_min,
389            vega_max,
390            theta_min,
391            theta_max,
392        } => {
393            let owner = owner.or(owner_arg).ok_or_else(|| {
394                anyhow::anyhow!("option-chain: 需要位置参数 <OWNER> 或 --owner / --code")
395            })?;
396            cmd::analysis::run_option_chain(
397                gateway,
398                &owner,
399                &begin,
400                &end,
401                &option_type,
402                cmd::analysis::OptionChainGreekFilterArgs {
403                    delta_min,
404                    delta_max,
405                    iv_min,
406                    iv_max,
407                    oi_min,
408                    oi_max,
409                    gamma_min,
410                    gamma_max,
411                    vega_min,
412                    vega_max,
413                    theta_min,
414                    theta_max,
415                },
416                output,
417            )
418            .await
419        }
420
421        // v1.4.30
422        Command::TradingDays { market, begin, end } => {
423            cmd::analysis::run_trading_days(gateway, &market, &begin, &end, output).await
424        }
425        Command::Rehab { symbol } => cmd::analysis::run_rehab(gateway, &symbol, output).await,
426        Command::Suspend {
427            symbols,
428            symbols_arg,
429            begin,
430            end,
431        } => {
432            let symbols = symbols.or(symbols_arg).ok_or_else(|| {
433                anyhow::anyhow!("suspend: 需要位置参数 <SYMBOLS> 或 --code / --symbols")
434            })?;
435            // v1.4.106 codex 0641 F6 (P3).
436            let syms = parse_symbol_csv(&symbols)?;
437            cmd::analysis::run_suspend(gateway, &syms, &begin, &end, output).await
438        }
439        Command::UserSecurity { group, group_arg } => {
440            let group = group
441                .or(group_arg)
442                .ok_or_else(|| anyhow::anyhow!("user-security: 需要位置参数 <GROUP> 或 --group"))?;
443            cmd::analysis::run_user_security(gateway, &group, output).await
444        }
445        Command::UserSecurityGroups { group_type } => {
446            cmd::analysis::run_user_security_groups(gateway, group_type, output).await
447        }
448        Command::Warrant { owner, begin, num } => {
449            cmd::analysis::run_warrant(gateway, owner.as_deref(), begin, num, output).await
450        }
451        Command::IpoList { market } => cmd::analysis::run_ipo_list(gateway, &market, output).await,
452        Command::IpoCalendar(args) => qot::dispatch_ipo_calendar(gateway, output, args).await,
453        Command::FutureInfo { symbols } => {
454            // v1.4.106 codex 0641 F6 (P3).
455            let syms = parse_symbol_csv(&symbols)?;
456            cmd::analysis::run_future_info(gateway, &syms, output).await
457        }
458        Command::StockFilter { market, begin, num } => {
459            cmd::analysis::run_stock_filter(gateway, &market, begin, num, output).await
460        }
461        Command::CancelAllOrder {
462            acc_id,
463            card_num,
464            env,
465            market,
466            jp_acc_type,
467            confirm,
468        } => {
469            trade_write::dispatch_cancel_all_order(
470                gateway,
471                output,
472                trade_write::CancelAllOrderDispatchArgs {
473                    acc_id,
474                    card_num,
475                    env,
476                    market,
477                    jp_acc_type,
478                    confirm,
479                },
480            )
481            .await
482        }
483        Command::GlobalState => cmd::sys::run_global_state(gateway, output).await,
484        Command::UserInfo => cmd::sys::run_user_info(gateway, output).await,
485        Command::QuoteRights(args) => {
486            cmd::sys::run_quote_rights(gateway, args.refresh, output).await
487        }
488        Command::QuoteCapability(args) => {
489            let symbol = args.symbol_positional.or(args.symbol_arg).ok_or_else(|| {
490                anyhow::anyhow!(
491                    "quote-capability: 需要 positional <SYMBOL> 或 --symbol/--code/--stock"
492                )
493            })?;
494            cmd::sys::run_quote_capability(gateway, &symbol, output).await
495        }
496        Command::DelayStatistics => cmd::sys::run_delay_statistics(gateway, output).await,
497        Command::TokenState => cmd::sys::run_token_state(gateway, output).await,
498        Command::RiskFreeRate => cmd::sys::run_risk_free_rate(gateway, output).await,
499        Command::SpreadTable => cmd::sys::run_spread_table(gateway, output).await,
500        Command::TickerStatistic(args) => {
501            // v1.4.102 A3 alias: positional 优先, 否则 --symbol named.
502            let sym = args.symbol_pos.or(args.symbol).ok_or_else(|| {
503                anyhow::anyhow!("ticker-statistic: SYMBOL or --symbol required (e.g. HK.00700)")
504            })?;
505            cmd::sys::run_ticker_statistic(gateway, &sym, args.ticker_type, args.stat_type, output)
506                .await
507        }
508        // v1.4.106 codex 0500 ζ23-redo: TickerStatistic Detail (cmd 6366)
509        Command::TickerStatisticDetail(args) => {
510            let sym = args.symbol_pos.or(args.symbol).ok_or_else(|| {
511                anyhow::anyhow!(
512                    "ticker-statistic-detail: SYMBOL or --symbol required (e.g. HK.00700)"
513                )
514            })?;
515            cmd::sys::run_ticker_statistic_detail(cmd::sys::TickerStatisticDetailCommand {
516                gateway,
517                symbol: &sym,
518                ticker_type: args.ticker_type,
519                ticker_time: args.ticker_time,
520                select_num: args.select_num,
521                data_from: args.data_from,
522                data_max_count: args.data_max_count,
523                stat_type: args.stat_type,
524                format: output,
525            })
526            .await
527        }
528
529        // v1.4.30 P2(100% 覆盖)
530        Command::QuerySubscription(args) => {
531            cmd::sys::run_query_subscription(gateway, args.all_conn, output).await
532        }
533        Command::UsedQuota => cmd::sys::run_used_quota(gateway, output).await,
534        Command::Unsubscribe(args) => {
535            let syms: Vec<String> = if args.symbols.trim().is_empty() {
536                vec![]
537            } else {
538                args.symbols
539                    .split(',')
540                    .map(|s| s.trim().to_string())
541                    .collect()
542            };
543            let types: Vec<i32> = if args.sub_types.trim().is_empty() {
544                vec![]
545            } else {
546                args.sub_types
547                    .split(',')
548                    .map(|s| s.trim().parse::<i32>())
549                    .collect::<std::result::Result<Vec<_>, _>>()
550                    .map_err(|e| anyhow::anyhow!("invalid sub-type: {e}"))?
551            };
552            cmd::sys::run_unsubscribe(gateway, &syms, &types, args.all, output).await
553        }
554        Command::Surface(args) => cmd::surface::run(args.gaps, args.checklist.as_deref(), output),
555        Command::HistoryKlQuota(args) => {
556            cmd::analysis::run_history_kl_quota(gateway, args.detail, output).await
557        }
558        Command::HoldingChange {
559            symbol,
560            category,
561            begin,
562            end,
563        } => {
564            cmd::analysis::run_holding_change(
565                gateway,
566                &symbol,
567                category,
568                begin.as_deref(),
569                end.as_deref(),
570                output,
571            )
572            .await
573        }
574        Command::ModifyUserSecurity { group, op, symbols } => {
575            let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
576            cmd::analysis::run_modify_user_security(gateway, &group, op, &syms, output).await
577        }
578        Command::CodeChange { symbols } => {
579            let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
580            cmd::analysis::run_code_change(gateway, &syms, output).await
581        }
582        Command::SetPriceReminder {
583            symbol,
584            op,
585            key,
586            r#type,
587            freq,
588            value,
589            note,
590            session,
591        } => {
592            cmd::analysis::run_set_price_reminder(cmd::analysis::SetPriceReminderCommand {
593                gateway,
594                symbol: &symbol,
595                op,
596                key,
597                reminder_type: r#type,
598                freq,
599                value,
600                note: note.as_deref(),
601                reminder_session_list: &session,
602            })
603            .await
604        }
605        Command::PriceReminder { symbol, market } => {
606            cmd::analysis::run_get_price_reminder(
607                gateway,
608                symbol.as_deref(),
609                market.as_deref(),
610                output,
611            )
612            .await
613        }
614        Command::OptionExpirationDate {
615            owner,
616            owner_arg,
617            index_type,
618        } => {
619            let owner = owner.or(owner_arg).ok_or_else(|| {
620                anyhow::anyhow!("option-expiration-date: 需要位置参数 <OWNER> 或 --owner")
621            })?;
622            cmd::analysis::run_option_expiration_date(gateway, &owner, index_type, output).await
623        }
624        Command::SubAccPush { acc_ids } => {
625            cmd::trade_ext::run_sub_acc_push(gateway, &acc_ids, output).await
626        }
627        Command::UnsubAccPush { acc_ids } => {
628            cmd::trade_ext::run_unsub_acc_push(gateway, &acc_ids, output).await
629        }
630        Command::AccCashFlow(args) => {
631            trade_write::dispatch_acc_cash_flow(gateway, output, args).await
632        }
633        Command::DaemonStatus(args) => {
634            daemon::dispatch_status(args.rest_url, args.rest_port, args.api_key, output).await
635        }
636        Command::Doctor(args) => daemon::dispatch_doctor(args, output).await,
637        Command::DaemonShutdown(args) => {
638            daemon::dispatch_shutdown(args.rest_url, args.rest_port, args.api_key).await
639        }
640        Command::DaemonReload(args) => {
641            daemon::dispatch_reload(args.rest_url, args.rest_port, args.api_key).await
642        }
643
644        // ====================================================================
645        // v1.4.94 / v1.4.95 Tier M (mobile-driven extensions, 11 endpoint)
646        // ====================================================================
647        Command::CashLog(args) => tier_m::dispatch_cash_log(gateway, output, args).await,
648        Command::CashDetail(args) => tier_m::dispatch_cash_detail(gateway, output, args).await,
649        Command::BizGroup(args) => tier_m::dispatch_biz_group(gateway, output, args).await,
650        Command::MarginInfo(args) => tier_m::dispatch_margin_info(gateway, output, args).await,
651        Command::AccountFlag(args) => tier_m::dispatch_account_flag(gateway, output, args).await,
652        Command::BondTotalAsset(args) => {
653            tier_m::dispatch_bond_total_asset(gateway, output, args).await
654        }
655        Command::BondSingleAsset(args) => {
656            tier_m::dispatch_bond_single_asset(gateway, output, args).await
657        }
658        Command::BondPositionList(args) => {
659            tier_m::dispatch_bond_position_list(gateway, output, args).await
660        }
661        Command::BondAnswerState(args) => {
662            tier_m::dispatch_bond_answer_state(gateway, output, args).await
663        }
664        Command::BondTradeReminder(args) => {
665            tier_m::dispatch_bond_trade_reminder(gateway, output, args).await
666        }
667    }
668}