1use std::sync::Arc;
4use std::time::Duration;
5
6use anyhow::{Context, Result, bail};
7use futu_net::client::{ClientConfig, FutuClient, PushReceiver, ReconnectingClient};
8use futu_net::reconnect::ReconnectPolicy;
9use futu_qot::types::{Security, SubType};
10
11const CLI_CONNECT_TOTAL_TIMEOUT: Duration = Duration::from_secs(6);
21const CLI_CONNECT_RETRY_DELAY: Duration = Duration::from_millis(200);
22const CLI_CONNECT_MAX_RETRIES: u32 = 4;
23
24pub fn parse_symbol(s: &str) -> Result<Security> {
25 futu_qot::symbol::parse_symbol(s)
26}
27
28pub fn format_symbol(sec: &Security) -> String {
30 futu_qot::symbol::format_symbol(sec)
31}
32
33pub fn parse_sub_type(s: &str) -> Result<SubType> {
35 let Some(t) = SubType::from_str_alias(s) else {
36 let other = s.trim().to_ascii_lowercase();
37 bail!(
38 "unknown sub type {other:?} (supported: basic, orderbook, orderbook_odd, ticker, rt, kl_day, kl_1min, kl_10min, kl_120min, ...)"
39 );
40 };
41 Ok(t)
42}
43
44pub fn parse_sub_types(csv: &str) -> Result<Vec<SubType>> {
46 csv.split(',')
47 .map(parse_sub_type)
48 .collect::<Result<Vec<_>>>()
49}
50
51pub fn parse_symbol_csv(s: &str) -> Result<Vec<String>> {
64 let trimmed = s.trim();
65 if trimmed.is_empty() {
66 bail!("CSV symbol 列表为空: 必须传入非空 \"MARKET.CODE\" 列表");
67 }
68 let mut out: Vec<String> = Vec::new();
69 for (i, token) in trimmed.split(',').enumerate() {
70 let t = token.trim();
71 if t.is_empty() {
72 bail!("CSV symbol[{i}] 为空 token (输入 \"{s}\"): 整体 reject, 不 silent skip 空项");
73 }
74 out.push(t.to_string());
75 }
76 Ok(out)
77}
78
79pub async fn connect_gateway(
83 addr: &str,
84 client_id: &str,
85) -> Result<(Arc<FutuClient>, PushReceiver)> {
86 let config = ClientConfig {
87 addr: addr.to_string(),
88 client_ver: env!("CARGO_PKG_VERSION").to_string(),
89 client_id: client_id.to_string(),
90 recv_notify: false,
91 rsa_key: None,
92 };
93 let policy = ReconnectPolicy::new(
94 CLI_CONNECT_RETRY_DELAY,
95 CLI_CONNECT_RETRY_DELAY,
96 Some(CLI_CONNECT_MAX_RETRIES),
97 );
98 let mut reconnector = ReconnectingClient::new(config).with_policy(policy);
99 let connect_result =
100 tokio::time::timeout(CLI_CONNECT_TOTAL_TIMEOUT, reconnector.connect()).await;
101 let (client, push_rx, _info) = match connect_result {
102 Ok(result) => result.with_context(|| format!("connect to futu gateway at {addr}"))?,
103 Err(_) => bail!(
104 "connect to futu gateway at {addr} timed out after {}s",
105 CLI_CONNECT_TOTAL_TIMEOUT.as_secs()
106 ),
107 };
108 Ok((Arc::new(client), push_rx))
109}
110
111#[cfg(test)]
112mod tests;