futu_backend/quote_sub/
ticker.rs1use prost::Message;
2
3use futu_core::error::{FutuError, Result};
4use futu_domain_qot_ticker::ensure_ticker_pull_backend_success;
5
6use crate::command_runtime::execute_qot_plaintext;
7use crate::conn::BackendConn;
8use crate::proto_internal::ft_cmd_tick;
9
10use super::{CMD_QOT_PULL_TICKER, ftapi_market_to_quote_mkt};
11
12const TICKER_FETCH_LATEST_KEY: u64 = u64::MAX;
14const TICKER_LATEST_DATE_TIME_S: u32 = u32::MAX;
16pub const TICKER_PAGE_MAX_ITEMS: u32 = 750;
17
18pub(super) mod nn_quote_session {
19 pub const RTH: i32 = 0;
20 pub const ETH: i32 = 1;
21 pub const ALL: i32 = 2;
22}
23
24pub(super) mod tick_period_type {
25 pub const NORMAL: u32 = 0;
27 pub const BEFORE: u32 = 1;
28 pub const AFTER: u32 = 2;
29 pub const OVERNIGHT: u32 = 4;
30}
31
32pub(super) fn common_session_to_nn(session: i32) -> i32 {
33 match session {
34 2 => nn_quote_session::ETH,
36 3 => nn_quote_session::ALL,
37 _ => nn_quote_session::RTH,
38 }
39}
40
41pub(super) fn ticker_periods_for_nn_session(nn_session: i32) -> Vec<u32> {
42 match nn_session {
43 nn_quote_session::ALL => vec![
44 tick_period_type::NORMAL,
45 tick_period_type::BEFORE,
46 tick_period_type::AFTER,
47 tick_period_type::OVERNIGHT,
48 ],
49 nn_quote_session::ETH => vec![
50 tick_period_type::NORMAL,
51 tick_period_type::BEFORE,
52 tick_period_type::AFTER,
53 ],
54 _ => vec![tick_period_type::NORMAL],
55 }
56}
57
58#[allow(deprecated)]
70pub async fn pull_latest_ticker(
71 backend: &BackendConn,
72 stock_id: u64,
73 nn_mkt_type: u8,
74 common_session: i32,
75 pull_count: u32,
76 broker_id: Option<i32>,
77) -> Result<ft_cmd_tick::TickRsp> {
78 pull_ticker_page(
79 backend,
80 stock_id,
81 nn_mkt_type,
82 common_session,
83 TICKER_FETCH_LATEST_KEY,
84 TICKER_LATEST_DATE_TIME_S,
85 pull_count.min(TICKER_PAGE_MAX_ITEMS),
86 broker_id,
87 )
88 .await
89}
90
91#[allow(deprecated)]
95pub async fn pull_ticker_page(
96 backend: &BackendConn,
97 stock_id: u64,
98 nn_mkt_type: u8,
99 common_session: i32,
100 begin_tick_key: u64,
101 date_time_s: u32,
102 pull_count: u32,
103 broker_id: Option<i32>,
104) -> Result<ft_cmd_tick::TickRsp> {
105 let (req, reserved) = build_ticker_page_request(
106 stock_id,
107 nn_mkt_type,
108 common_session,
109 begin_tick_key,
110 date_time_s,
111 pull_count,
112 broker_id,
113 )?;
114 let frame = execute_qot_plaintext(
115 backend,
116 CMD_QOT_PULL_TICKER,
117 req.encode_to_vec().into(),
118 reserved,
119 )
120 .await?;
121 let rsp: ft_cmd_tick::TickRsp = Message::decode(frame.body.as_ref())?;
122 if let Err(reject) = ensure_ticker_pull_backend_success(rsp.result) {
123 return Err(FutuError::ServerError {
124 ret_type: reject.result,
125 msg: format!("CMD6128 PullLatestTicker result={}", reject.result),
126 });
127 }
128 Ok(rsp)
129}
130
131#[allow(deprecated)]
132pub(super) fn build_ticker_page_request(
133 stock_id: u64,
134 nn_mkt_type: u8,
135 common_session: i32,
136 begin_tick_key: u64,
137 date_time_s: u32,
138 pull_count: u32,
139 broker_id: Option<i32>,
140) -> Result<(ft_cmd_tick::TickReq, [u8; 10])> {
141 if stock_id == 0 || pull_count == 0 {
142 return Err(FutuError::Codec(format!(
143 "PullLatestTicker: invalid stock_id={stock_id} pull_count={pull_count}"
144 )));
145 }
146 if pull_count > TICKER_PAGE_MAX_ITEMS {
147 return Err(FutuError::Codec(format!(
148 "PullLatestTicker: page pull_count={pull_count} exceeds {TICKER_PAGE_MAX_ITEMS}"
149 )));
150 }
151
152 let nn_session = if nn_mkt_type == ftapi_market_to_quote_mkt(11) {
155 common_session_to_nn(common_session)
156 } else {
157 nn_quote_session::RTH
158 };
159
160 let req = ft_cmd_tick::TickReq {
161 security_id: Some(stock_id),
162 date_time_s: Some(date_time_s),
163 begin_tick_key: Some(begin_tick_key),
164 tick_count: Some(pull_count),
165 tick_period_type: None,
166 tick_period_type_ex: ticker_periods_for_nn_session(nn_session),
167 req_auth: None,
168 end_tick_key: None,
169 date_time_s_v2: None,
170 broker_id,
173 };
174
175 let mut reserved = [0u8; 10];
176 reserved[0] = nn_mkt_type;
177 Ok((req, reserved))
180}