Skip to main content

futu_qot/
history_kl.rs

1use futu_core::error::{FutuError, Result};
2use futu_core::proto_id;
3use futu_net::client::FutuClient;
4
5use crate::subscription_plan::SESSION_OVERNIGHT;
6use crate::types::{KLType, KLine, RehabType, Security};
7
8/// 历史 K 线查询结果
9#[derive(Debug, Clone)]
10pub struct HistoryKLResult {
11    pub security: Security,
12    pub kl_list: Vec<KLine>,
13    /// 下一页 K 线时间(分页时使用)
14    pub next_kl_time: Option<String>,
15    /// 下次请求的分页 key,None 表示数据已全部返回
16    pub next_req_key: Option<Vec<u8>>,
17}
18
19/// 历史 K 线可选参数。
20#[derive(Debug, Clone, Copy, Default)]
21pub struct HistoryKLOptions<'a> {
22    /// 指定返回 K 线字段,取 `Qot_Common.KLFields` bitmask。
23    pub need_kl_fields_flag: Option<i64>,
24    /// 分页请求 key。
25    pub next_req_key: Option<&'a [u8]>,
26    /// 是否获取美股盘前盘后数据。
27    pub extended_time: Option<bool>,
28    /// `Common.Session`,C++ RequestHistoryKL 明确拒绝 `Session_OVERNIGHT(4)`。
29    pub session: Option<i32>,
30}
31
32/// 获取历史 K 线数据
33///
34/// 按时间范围查询历史 K 线。
35/// - `begin_time`: 开始时间,格式 "yyyy-MM-dd"
36/// - `end_time`: 结束时间,格式 "yyyy-MM-dd"
37/// - `max_num`: 最多返回根数,None 表示不限制
38pub async fn get_history_kl(
39    client: &FutuClient,
40    security: &Security,
41    rehab_type: RehabType,
42    kl_type: KLType,
43    begin_time: &str,
44    end_time: &str,
45    max_num: Option<i32>,
46) -> Result<HistoryKLResult> {
47    get_history_kl_with_options(
48        client,
49        security,
50        rehab_type,
51        kl_type,
52        begin_time,
53        end_time,
54        max_num,
55        HistoryKLOptions::default(),
56    )
57    .await
58}
59
60/// 获取历史 K 线数据(带完整 RequestHistoryKL 可选字段)。
61pub async fn get_history_kl_with_options(
62    client: &FutuClient,
63    security: &Security,
64    rehab_type: RehabType,
65    kl_type: KLType,
66    begin_time: &str,
67    end_time: &str,
68    max_num: Option<i32>,
69    options: HistoryKLOptions<'_>,
70) -> Result<HistoryKLResult> {
71    let result = request_history_kl(
72        client,
73        &RequestHistoryKLParams {
74            security,
75            rehab_type,
76            kl_type,
77            begin_time,
78            end_time,
79            max_num,
80            need_kl_fields_flag: options.need_kl_fields_flag,
81            next_req_key: options.next_req_key,
82            extended_time: options.extended_time,
83            session: options.session,
84        },
85    )
86    .await?;
87
88    Ok(HistoryKLResult {
89        security: result.security,
90        kl_list: result.kl_list,
91        next_kl_time: None, // field removed in new proto; use next_req_key for pagination
92        next_req_key: result.next_req_key,
93    })
94}
95
96/// 分页请求历史 K 线参数
97#[derive(Debug, Clone)]
98pub struct RequestHistoryKLParams<'a> {
99    pub security: &'a Security,
100    pub rehab_type: RehabType,
101    pub kl_type: KLType,
102    pub begin_time: &'a str,
103    pub end_time: &'a str,
104    pub max_num: Option<i32>,
105    pub need_kl_fields_flag: Option<i64>,
106    pub next_req_key: Option<&'a [u8]>,
107    pub extended_time: Option<bool>,
108    pub session: Option<i32>,
109}
110
111/// 分页请求历史 K 线结果
112#[derive(Debug, Clone)]
113pub struct RequestHistoryKLResult {
114    pub security: Security,
115    pub kl_list: Vec<KLine>,
116    /// 下次请求的分页 key,None 表示数据已全部返回
117    pub next_req_key: Option<Vec<u8>>,
118}
119
120/// 分页请求历史 K 线(支持大数据量分页拉取)
121pub async fn request_history_kl(
122    client: &FutuClient,
123    params: &RequestHistoryKLParams<'_>,
124) -> Result<RequestHistoryKLResult> {
125    validate_history_kline_session(params.session)?;
126
127    let req = futu_proto::qot_request_history_kl::Request {
128        c2s: futu_proto::qot_request_history_kl::C2s {
129            rehab_type: params.rehab_type as i32,
130            kl_type: params.kl_type as i32,
131            security: params.security.to_proto(),
132            begin_time: params.begin_time.to_string(),
133            end_time: params.end_time.to_string(),
134            max_ack_kl_num: params.max_num,
135            need_kl_fields_flag: params.need_kl_fields_flag,
136            next_req_key: params.next_req_key.map(|k| k.to_vec()),
137            extended_time: params.extended_time,
138            session: params.session,
139            header: None,
140        },
141    };
142
143    let body = prost::Message::encode_to_vec(&req);
144    let resp_frame = client
145        .request(proto_id::QOT_REQUEST_HISTORY_KL, body)
146        .await?;
147
148    let resp: futu_proto::qot_request_history_kl::Response =
149        prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
150
151    if resp.ret_type != 0 {
152        return Err(crate::server_err(resp.ret_type, resp.ret_msg));
153    }
154
155    let s2c = resp
156        .s2c
157        .ok_or(FutuError::Codec("missing s2c in RequestHistoryKL".into()))?;
158
159    Ok(RequestHistoryKLResult {
160        security: Security::from_proto(&s2c.security),
161        kl_list: s2c.kl_list.iter().map(KLine::from_proto).collect(),
162        next_req_key: s2c.next_req_key,
163    })
164}
165
166fn validate_history_kline_session(session: Option<i32>) -> Result<()> {
167    if session == Some(SESSION_OVERNIGHT) {
168        return Err(FutuError::Codec(
169            "RequestHistoryKL: session=OVERNIGHT (4) is not supported".into(),
170        ));
171    }
172    Ok(())
173}
174
175#[cfg(test)]
176mod tests;