Skip to main content

futucli/cli/dispatch/
custom.rs

1//! Explicit wrappers for non-mechanical root dispatch paths.
2
3use super::super::commands::*;
4use super::{daemon, trade_write};
5use crate::cmd;
6use crate::common::parse_symbol_csv;
7use crate::output::OutputFormat;
8use anyhow::Result;
9pub(super) async fn dispatch_verification(
10    gateway: &str,
11    output: OutputFormat,
12    args: VerificationArgs,
13) -> Result<()> {
14    cmd::proto_json::run_verification(gateway, args.verification_type, args.op, args.code, output)
15        .await
16}
17pub(super) async fn dispatch_indicator_calc(
18    gateway: &str,
19    output: OutputFormat,
20    args: IndicatorCalcArgs,
21) -> Result<()> {
22    cmd::indicator::calculate(gateway, &args.c2s_json, args.timeout_secs, output).await
23}
24pub(super) async fn dispatch_commands(
25    gateway: &str,
26    output: OutputFormat,
27    args: CommandsArgs,
28) -> Result<()> {
29    let _ = (gateway, &output);
30    cmd::command_catalog::run(args.group.as_deref(), args.search.as_deref(), output)
31}
32pub(super) async fn dispatch_version(
33    gateway: &str,
34    output: OutputFormat,
35    args: VersionArgs,
36) -> Result<()> {
37    let _ = (gateway, &output);
38    {
39        cmd::version_check::run_version(args.check, args.url.as_deref(), args.timeout_ms, output)
40            .await
41    }
42}
43pub(super) async fn dispatch_lang_pack(
44    gateway: &str,
45    output: OutputFormat,
46    args: LanguagePackArgs,
47) -> Result<()> {
48    let _ = (gateway, &output);
49    cmd::lang_pack::run(args.command, output).await
50}
51pub(super) async fn dispatch_push_subscriber_info(
52    gateway: &str,
53    output: OutputFormat,
54    args: PushSubscriberInfoArgs,
55) -> Result<()> {
56    let _ = (gateway, &output);
57    daemon::dispatch_push_sub_info(args, output).await
58}
59pub(super) async fn dispatch_static_status(
60    gateway: &str,
61    output: OutputFormat,
62    args: StaticStatusArgs,
63) -> Result<()> {
64    let _ = (gateway, &output);
65    {
66        let rest_url = args
67            .rest_url
68            .or_else(|| args.rest_port.map(|p| format!("http://127.0.0.1:{p}")));
69        cmd::static_diag::run_static_status(rest_url.as_deref(), args.api_key.as_deref(), output)
70            .await
71    }
72}
73pub(super) async fn dispatch_static_warmup(
74    gateway: &str,
75    output: OutputFormat,
76    args: StaticWarmupArgs,
77) -> Result<()> {
78    let _ = (gateway, &output);
79    cmd::static_info::run(gateway, &args.symbols, output).await
80}
81pub(super) async fn dispatch_unlock_trade(
82    gateway: &str,
83    output: OutputFormat,
84    args: UnlockTradeArgs,
85) -> Result<()> {
86    let _ = (gateway, &output);
87    {
88        cmd::unlock::run(
89            gateway,
90            args.lock,
91            args.from_stdin,
92            args.trade_pwd_account.as_deref(),
93            args.otp,
94            args.security_firm,
95            args.acc_ids,
96            output,
97        )
98        .await
99    }
100}
101pub(super) async fn dispatch_set_trade_pwd(
102    gateway: &str,
103    output: OutputFormat,
104    args: SetTradePwdArgs,
105) -> Result<()> {
106    let _ = (gateway, &output);
107    cmd::unlock::set_trade_pwd(&args.account, args.from_stdin).await
108}
109pub(super) async fn dispatch_clear_trade_pwd(
110    gateway: &str,
111    output: OutputFormat,
112    args: ClearTradePwdArgs,
113) -> Result<()> {
114    let _ = (gateway, &output);
115    cmd::unlock::clear_trade_pwd(&args.account).await
116}
117pub(super) async fn dispatch_set_login_pwd(
118    gateway: &str,
119    output: OutputFormat,
120    args: SetLoginPwdArgs,
121) -> Result<()> {
122    let _ = (gateway, &output);
123    cmd::unlock::set_login_pwd(&args.account, args.from_stdin).await
124}
125pub(super) async fn dispatch_clear_login_pwd(
126    gateway: &str,
127    output: OutputFormat,
128    args: ClearLoginPwdArgs,
129) -> Result<()> {
130    let _ = (gateway, &output);
131    cmd::unlock::clear_login_pwd(&args.account).await
132}
133pub(super) async fn dispatch_repl(gateway: &str, output: OutputFormat) -> Result<()> {
134    let _ = (gateway, &output);
135    {
136        anyhow::bail!(
137            "cannot enter REPL from this context (already nested / not a top-level invocation)"
138        )
139    }
140}
141pub(super) async fn dispatch_gen_key(
142    gateway: &str,
143    output: OutputFormat,
144    args: GenKeyArgs,
145) -> Result<()> {
146    let _ = gateway;
147    {
148        cmd::gen_key::run(cmd::gen_key::GenKeyCommand {
149            id: args.id,
150            scopes: args.scopes,
151            keys_file: args.keys_file,
152            expires: args.expires,
153            note: args.note,
154            allowed_markets: args.allowed_markets,
155            allowed_symbols: args.allowed_symbols,
156            max_order_value: args.max_order_value,
157            max_daily_value: args.max_daily_value,
158            hours_window: args.hours_window,
159            max_orders_per_minute: args.max_orders_per_minute,
160            allowed_trd_sides: args.allowed_trd_sides,
161            allowed_acc_ids: args.allowed_acc_ids,
162            allowed_card_nums: args.allowed_card_nums,
163            bind_this_machine: args.bind_this_machine,
164            bind_machines: args.bind_machines,
165            output,
166        })
167        .await
168    }
169}
170pub(super) async fn dispatch_bind_key(
171    gateway: &str,
172    output: OutputFormat,
173    args: BindKeyArgs,
174) -> Result<()> {
175    let _ = (gateway, &output);
176    {
177        cmd::bind_key::run(cmd::bind_key::BindKeyCommand {
178            id: args.id,
179            keys_file: args.keys_file,
180            this_machine: args.this_machine,
181            machines: args.machines,
182            replace: args.replace,
183            clear: args.clear,
184            freeze: args.freeze,
185        })
186        .await
187    }
188}
189pub(super) async fn dispatch_machine_id(
190    gateway: &str,
191    output: OutputFormat,
192    args: MachineIdArgs,
193) -> Result<()> {
194    let _ = (gateway, &output);
195    cmd::machine::run(args.for_key).await
196}
197pub(super) async fn dispatch_list_keys(
198    gateway: &str,
199    output: OutputFormat,
200    args: ListKeysArgs,
201) -> Result<()> {
202    let _ = (gateway, &output);
203    cmd::keys::list(args.keys_file, args.json).await
204}
205pub(super) async fn dispatch_revoke_key(
206    gateway: &str,
207    output: OutputFormat,
208    args: RevokeKeyArgs,
209) -> Result<()> {
210    let _ = (gateway, &output);
211    cmd::keys::revoke(args.id, args.keys_file, args.yes).await
212}
213pub(super) async fn dispatch_combo_order(
214    gateway: &str,
215    output: OutputFormat,
216    args: ComboOrderArgs,
217) -> Result<()> {
218    let _ = (gateway, &output);
219    {
220        cmd::proto_json::run_place_combo_order(
221            gateway,
222            &args.c2s_json,
223            args.confirm,
224            args.idempotency_key,
225            output,
226        )
227        .await
228    }
229}
230pub(super) async fn dispatch_capital_distribution(
231    gateway: &str,
232    output: OutputFormat,
233    args: CapitalDistributionArgs,
234) -> Result<()> {
235    let _ = (gateway, &output);
236    let CapitalDistributionArgs { symbol } = args;
237    cmd::analysis::run_capital_distribution(gateway, &symbol, output).await
238}
239pub(super) async fn dispatch_market_state(
240    gateway: &str,
241    output: OutputFormat,
242    args: MarketStateArgs,
243) -> Result<()> {
244    let _ = (gateway, &output);
245    let MarketStateArgs { symbols } = args;
246    {
247        let list = parse_symbol_csv(&symbols)?;
248        cmd::analysis::run_market_state(gateway, &list, output).await
249    }
250}
251pub(super) async fn dispatch_owner_plate(
252    gateway: &str,
253    output: OutputFormat,
254    args: OwnerPlateArgs,
255) -> Result<()> {
256    let _ = (gateway, &output);
257    let OwnerPlateArgs { symbols } = args;
258    {
259        let list = parse_symbol_csv(&symbols)?;
260        cmd::analysis::run_owner_plate(gateway, &list, output).await
261    }
262}
263pub(super) async fn dispatch_option_chain(
264    gateway: &str,
265    output: OutputFormat,
266    args: OptionChainArgs,
267) -> Result<()> {
268    let _ = (gateway, &output);
269    let OptionChainArgs {
270        owner,
271        owner_arg,
272        begin,
273        end,
274        option_type,
275        delta_min,
276        delta_max,
277        iv_min,
278        iv_max,
279        oi_min,
280        oi_max,
281        gamma_min,
282        gamma_max,
283        vega_min,
284        vega_max,
285        theta_min,
286        theta_max,
287    } = args;
288    {
289        let owner = owner.or(owner_arg).ok_or_else(|| {
290            anyhow::anyhow!("option-chain: 需要位置参数 <OWNER> 或 --owner / --code")
291        })?;
292        cmd::analysis::run_option_chain(
293            gateway,
294            &owner,
295            &begin,
296            &end,
297            &option_type,
298            cmd::analysis::OptionChainGreekFilterArgs {
299                delta_min,
300                delta_max,
301                iv_min,
302                iv_max,
303                oi_min,
304                oi_max,
305                gamma_min,
306                gamma_max,
307                vega_min,
308                vega_max,
309                theta_min,
310                theta_max,
311            },
312            output,
313        )
314        .await
315    }
316}
317pub(super) async fn dispatch_trading_days(
318    gateway: &str,
319    output: OutputFormat,
320    args: TradingDaysArgs,
321) -> Result<()> {
322    let _ = (gateway, &output);
323    let TradingDaysArgs { market, begin, end } = args;
324    cmd::analysis::run_trading_days(gateway, &market, &begin, &end, output).await
325}
326pub(super) async fn dispatch_rehab(
327    gateway: &str,
328    output: OutputFormat,
329    args: RehabArgs,
330) -> Result<()> {
331    let _ = (gateway, &output);
332    let RehabArgs { symbol } = args;
333    cmd::analysis::run_rehab(gateway, &symbol, output).await
334}
335pub(super) async fn dispatch_suspend(
336    gateway: &str,
337    output: OutputFormat,
338    args: SuspendArgs,
339) -> Result<()> {
340    let _ = (gateway, &output);
341    let SuspendArgs {
342        symbols,
343        symbols_arg,
344        begin,
345        end,
346    } = args;
347    {
348        let symbols = symbols.or(symbols_arg).ok_or_else(|| {
349            anyhow::anyhow!("suspend: 需要位置参数 <SYMBOLS> 或 --code / --symbols")
350        })?;
351        let syms = parse_symbol_csv(&symbols)?;
352        cmd::analysis::run_suspend(gateway, &syms, &begin, &end, output).await
353    }
354}
355pub(super) async fn dispatch_user_security(
356    gateway: &str,
357    output: OutputFormat,
358    args: UserSecurityArgs,
359) -> Result<()> {
360    let _ = (gateway, &output);
361    let UserSecurityArgs { group, group_arg } = args;
362    {
363        let group = group
364            .or(group_arg)
365            .ok_or_else(|| anyhow::anyhow!("user-security: 需要位置参数 <GROUP> 或 --group"))?;
366        cmd::analysis::run_user_security(gateway, &group, output).await
367    }
368}
369pub(super) async fn dispatch_user_security_groups(
370    gateway: &str,
371    output: OutputFormat,
372    args: UserSecurityGroupsArgs,
373) -> Result<()> {
374    let _ = (gateway, &output);
375    let UserSecurityGroupsArgs { group_type } = args;
376    cmd::analysis::run_user_security_groups(gateway, group_type, output).await
377}
378pub(super) async fn dispatch_warrant(
379    gateway: &str,
380    output: OutputFormat,
381    args: WarrantArgs,
382) -> Result<()> {
383    let _ = (gateway, &output);
384    let WarrantArgs { owner, begin, num } = args;
385    cmd::analysis::run_warrant(gateway, owner.as_deref(), begin, num, output).await
386}
387pub(super) async fn dispatch_ipo_list(
388    gateway: &str,
389    output: OutputFormat,
390    args: IpoListArgs,
391) -> Result<()> {
392    let _ = (gateway, &output);
393    let IpoListArgs { market } = args;
394    cmd::analysis::run_ipo_list(gateway, &market, output).await
395}
396pub(super) async fn dispatch_future_info(
397    gateway: &str,
398    output: OutputFormat,
399    args: FutureInfoArgs,
400) -> Result<()> {
401    let _ = (gateway, &output);
402    let FutureInfoArgs { symbols } = args;
403    {
404        let syms = parse_symbol_csv(&symbols)?;
405        cmd::analysis::run_future_info(gateway, &syms, output).await
406    }
407}
408pub(super) async fn dispatch_stock_filter(
409    gateway: &str,
410    output: OutputFormat,
411    args: StockFilterArgs,
412) -> Result<()> {
413    let _ = (gateway, &output);
414    let StockFilterArgs { market, begin, num } = args;
415    cmd::analysis::run_stock_filter(gateway, &market, begin, num, output).await
416}
417pub(super) async fn dispatch_cancel_all_order(
418    gateway: &str,
419    output: OutputFormat,
420    args: CancelAllOrderArgs,
421) -> Result<()> {
422    let _ = (gateway, &output);
423    let CancelAllOrderArgs {
424        acc_id,
425        card_num,
426        acc_id_file,
427        env,
428        market,
429        jp_acc_type,
430        confirm,
431    } = args;
432    {
433        trade_write::dispatch_cancel_all_order(
434            gateway,
435            output,
436            trade_write::CancelAllOrderDispatchArgs {
437                acc_id,
438                acc_id_file,
439                card_num,
440                env,
441                market,
442                jp_acc_type,
443                confirm,
444            },
445        )
446        .await
447    }
448}
449pub(super) async fn dispatch_quote_rights(
450    gateway: &str,
451    output: OutputFormat,
452    args: QuoteRightsArgs,
453) -> Result<()> {
454    let _ = (gateway, &output);
455    cmd::sys::run_quote_rights(gateway, args.refresh, output).await
456}
457pub(super) async fn dispatch_quote_capability(
458    gateway: &str,
459    output: OutputFormat,
460    args: QuoteCapabilityArgs,
461) -> Result<()> {
462    let _ = (gateway, &output);
463    {
464        let symbol = args.symbol_positional.or(args.symbol_arg).ok_or_else(|| {
465            anyhow::anyhow!("quote-capability: 需要 positional <SYMBOL> 或 --symbol/--code/--stock")
466        })?;
467        cmd::sys::run_quote_capability(gateway, &symbol, output).await
468    }
469}
470pub(super) async fn dispatch_ticker_statistic(
471    gateway: &str,
472    output: OutputFormat,
473    args: TickerStatisticArgs,
474) -> Result<()> {
475    let _ = (gateway, &output);
476    {
477        let sym = args.symbol_pos.or(args.symbol).ok_or_else(|| {
478            anyhow::anyhow!("ticker-statistic: SYMBOL or --symbol required (e.g. HK.00700)")
479        })?;
480        cmd::sys::run_ticker_statistic(gateway, &sym, args.ticker_type, args.stat_type, output)
481            .await
482    }
483}
484pub(super) async fn dispatch_ticker_statistic_detail(
485    gateway: &str,
486    output: OutputFormat,
487    args: TickerStatisticDetailArgs,
488) -> Result<()> {
489    let _ = (gateway, &output);
490    {
491        let sym = args.symbol_pos.or(args.symbol).ok_or_else(|| {
492            anyhow::anyhow!("ticker-statistic-detail: SYMBOL or --symbol required (e.g. HK.00700)")
493        })?;
494        cmd::sys::run_ticker_statistic_detail(cmd::sys::TickerStatisticDetailCommand {
495            gateway,
496            symbol: &sym,
497            ticker_type: args.ticker_type,
498            ticker_time: args.ticker_time,
499            select_num: args.select_num,
500            data_from: args.data_from,
501            data_max_count: args.data_max_count,
502            stat_type: args.stat_type,
503            format: output,
504        })
505        .await
506    }
507}
508pub(super) async fn dispatch_query_subscription(
509    gateway: &str,
510    output: OutputFormat,
511    args: QuerySubscriptionArgs,
512) -> Result<()> {
513    let _ = (gateway, &output);
514    cmd::sys::run_query_subscription(gateway, args.all_conn, output).await
515}
516pub(super) async fn dispatch_unsubscribe(
517    gateway: &str,
518    output: OutputFormat,
519    args: UnsubscribeArgs,
520) -> Result<()> {
521    let _ = (gateway, &output);
522    {
523        let syms: Vec<String> = if args.symbols.trim().is_empty() {
524            vec![]
525        } else {
526            args.symbols
527                .split(',')
528                .map(|s| s.trim().to_string())
529                .collect()
530        };
531        let types: Vec<i32> = if args.sub_types.trim().is_empty() {
532            vec![]
533        } else {
534            args.sub_types
535                .split(',')
536                .map(|s| s.trim().parse::<i32>())
537                .collect::<std::result::Result<Vec<_>, _>>()
538                .map_err(|e| anyhow::anyhow!("invalid sub-type: {e}"))?
539        };
540        cmd::sys::run_unsubscribe(gateway, &syms, &types, args.all, output).await
541    }
542}
543pub(super) async fn dispatch_surface(
544    gateway: &str,
545    output: OutputFormat,
546    args: SurfaceArgs,
547) -> Result<()> {
548    let _ = (gateway, &output);
549    cmd::surface::run(args.gaps, args.checklist.as_deref(), output)
550}
551pub(super) async fn dispatch_history_kl_quota(
552    gateway: &str,
553    output: OutputFormat,
554    args: HistoryKlQuotaArgs,
555) -> Result<()> {
556    let _ = (gateway, &output);
557    cmd::analysis::run_history_kl_quota(gateway, args.detail, output).await
558}
559pub(super) async fn dispatch_holding_change(
560    gateway: &str,
561    output: OutputFormat,
562    args: HoldingChangeArgs,
563) -> Result<()> {
564    let _ = (gateway, &output);
565    let HoldingChangeArgs {
566        symbol,
567        category,
568        begin,
569        end,
570    } = args;
571    {
572        cmd::analysis::run_holding_change(
573            gateway,
574            &symbol,
575            category,
576            begin.as_deref(),
577            end.as_deref(),
578            output,
579        )
580        .await
581    }
582}
583pub(super) async fn dispatch_modify_user_security(
584    gateway: &str,
585    output: OutputFormat,
586    args: ModifyUserSecurityArgs,
587) -> Result<()> {
588    let _ = (gateway, &output);
589    let ModifyUserSecurityArgs { group, op, symbols } = args;
590    {
591        let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
592        cmd::analysis::run_modify_user_security(gateway, &group, op, &syms, output).await
593    }
594}
595pub(super) async fn dispatch_code_change(
596    gateway: &str,
597    output: OutputFormat,
598    args: CodeChangeArgs,
599) -> Result<()> {
600    let _ = (gateway, &output);
601    let CodeChangeArgs { symbols } = args;
602    {
603        let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
604        cmd::analysis::run_code_change(gateway, &syms, output).await
605    }
606}
607pub(super) async fn dispatch_set_price_reminder(
608    gateway: &str,
609    output: OutputFormat,
610    args: SetPriceReminderArgs,
611) -> Result<()> {
612    let _ = (gateway, &output);
613    let SetPriceReminderArgs {
614        symbol,
615        op,
616        key,
617        r#type,
618        freq,
619        value,
620        note,
621        session,
622    } = args;
623    {
624        cmd::analysis::run_set_price_reminder(cmd::analysis::SetPriceReminderCommand {
625            gateway,
626            symbol: &symbol,
627            op,
628            key,
629            reminder_type: r#type,
630            freq,
631            value,
632            note: note.as_deref(),
633            reminder_session_list: &session,
634        })
635        .await
636    }
637}
638pub(super) async fn dispatch_price_reminder(
639    gateway: &str,
640    output: OutputFormat,
641    args: PriceReminderArgs,
642) -> Result<()> {
643    let _ = (gateway, &output);
644    let PriceReminderArgs { symbol, market } = args;
645    {
646        cmd::analysis::run_get_price_reminder(gateway, symbol.as_deref(), market.as_deref(), output)
647            .await
648    }
649}
650pub(super) async fn dispatch_option_expiration_date(
651    gateway: &str,
652    output: OutputFormat,
653    args: OptionExpirationDateArgs,
654) -> Result<()> {
655    let _ = (gateway, &output);
656    let OptionExpirationDateArgs {
657        owner,
658        owner_arg,
659        index_type,
660    } = args;
661    {
662        let owner = owner.or(owner_arg).ok_or_else(|| {
663            anyhow::anyhow!("option-expiration-date: 需要位置参数 <OWNER> 或 --owner")
664        })?;
665        cmd::analysis::run_option_expiration_date(gateway, &owner, index_type, output).await
666    }
667}
668pub(super) async fn dispatch_sub_acc_push(
669    gateway: &str,
670    output: OutputFormat,
671    args: SubAccPushArgs,
672) -> Result<()> {
673    let _ = (gateway, &output);
674    let SubAccPushArgs { acc_ids } = args;
675    cmd::trade_ext::run_sub_acc_push(gateway, &acc_ids, output).await
676}
677pub(super) async fn dispatch_unsub_acc_push(
678    gateway: &str,
679    output: OutputFormat,
680    args: UnsubAccPushArgs,
681) -> Result<()> {
682    let _ = (gateway, &output);
683    let UnsubAccPushArgs { acc_ids } = args;
684    cmd::trade_ext::run_unsub_acc_push(gateway, &acc_ids, output).await
685}
686pub(super) async fn dispatch_daemon_status(
687    gateway: &str,
688    output: OutputFormat,
689    args: DaemonStatusArgs,
690) -> Result<()> {
691    let _ = (gateway, &output);
692    daemon::dispatch_status(args.rest_url, args.rest_port, args.api_key, output).await
693}
694pub(super) async fn dispatch_doctor(
695    gateway: &str,
696    output: OutputFormat,
697    args: DoctorArgs,
698) -> Result<()> {
699    let _ = (gateway, &output);
700    daemon::dispatch_doctor(args, output).await
701}
702pub(super) async fn dispatch_daemon_shutdown(
703    gateway: &str,
704    output: OutputFormat,
705    args: DaemonShutdownArgs,
706) -> Result<()> {
707    let _ = (gateway, &output);
708    daemon::dispatch_shutdown(args.rest_url, args.rest_port, args.api_key).await
709}
710pub(super) async fn dispatch_daemon_reload(
711    gateway: &str,
712    output: OutputFormat,
713    args: DaemonReloadArgs,
714) -> Result<()> {
715    let _ = (gateway, &output);
716    daemon::dispatch_reload(args.rest_url, args.rest_port, args.api_key).await
717}