rustdx/tcp/
basic.rs

1use super::{u16_from_le_bytes, Result, Tdx};
2
3/// 心跳包。用于保持 Tcp 连接。
4pub type Heartbeat = SecurityCount;
5
6/// 对应 pytdx 的 hq.security_count / GetSecurityCountCmd
7/// (深沪市证券数量,包括指数、股票和大量债券)、心跳包。
8#[derive(Debug)]
9pub struct SecurityCount {
10    send: Box<[u8]>,
11    /// 0 代表深市;1 代表沪市。
12    market: u16,
13    /// 响应的结果:证券数量
14    count: u16,
15}
16
17impl SecurityCount {
18    /// market = 0 或 1,表示深市或沪市。
19    pub fn new(market: u16) -> Self {
20        let mut send = [0; Self::LEN];
21        send.copy_from_slice(Self::SEND);
22        if market != 0 {
23            send[12..14].copy_from_slice(&market.to_le_bytes());
24        }
25        Self {
26            send: send.into(),
27            market,
28            count: 0,
29        }
30    }
31
32    pub fn market(&mut self, market: u16) {
33        self.market = market;
34        self.send[12..14].copy_from_slice(&market.to_le_bytes());
35    }
36}
37
38impl Tdx for SecurityCount {
39    type Item = u16;
40
41    /// 深市证券数量的请求字节。
42    const SEND: &'static [u8] = &[
43        0x0c, 0x0c, 0x18, 0x6c, 0x00, 0x01, 0x08, 0x00, 0x08, 0x00, 0x4e, 0x04, 0x00, 0x00, 0x75,
44        0xc7, 0x33, 0x01,
45    ];
46    const TAG: &'static str = "heartbeat";
47
48    fn send(&mut self) -> &[u8] {
49        &self.send
50    }
51
52    fn parse(&mut self, response: Vec<u8>) {
53        self.count = u16_from_le_bytes(&response, 0);
54    }
55
56    fn result(&self) -> &Self::Item {
57        &self.count
58    }
59}
60
61/// 查询证券列表。对应于 pytdx 中的 GetSecurityList。
62///
63/// ## 注意:
64/// - 获取的数据可能同一类别内是有序的,不同类别间是顺序未知 (比如 A
65///   股股票之后不是创业板/科创板股票);
66/// - 每次返回 1000 条结果。
67#[derive(Debug, Clone)]
68pub struct SecurityList {
69    pub send: Box<[u8]>,
70    pub market: u16,
71    pub start: u16,
72    /// 响应信息中的列表长度。
73    pub count: usize,
74    pub response: Vec<u8>,
75    pub data: Box<[SecurityListData]>,
76}
77
78impl Default for SecurityList {
79    fn default() -> Self {
80        Self {
81            send: {
82                let mut arr = [0; Self::LEN];
83                arr.copy_from_slice(Self::SEND);
84                arr.into()
85            },
86            market: 0,
87            start: 1,
88            count: 0,
89            response: Vec::new(),
90            data: [].into(),
91        }
92    }
93}
94
95impl SecurityList {
96    /// 参数说明:
97    /// - market = 0 或 1,表示深市或沪市;
98    /// - start 在 [0, n] 的范围内,其中 n 是 [`SecurityCount`] 得到的结果。 目前 market = 0
99    ///   时,有 13471 条; market = 1 时,有 18065 条。
100    pub fn new(market: u16, start: u16) -> Self {
101        Self {
102            send: {
103                let mut arr = [0; Self::LEN];
104                arr.copy_from_slice(Self::SEND);
105                arr[12..14].copy_from_slice(&market.to_le_bytes());
106                arr[14..16].copy_from_slice(&start.to_le_bytes());
107                arr.into()
108            },
109            market,
110            start,
111            count: 0,
112            response: Vec::new(),
113            data: [].into(),
114        }
115    }
116}
117
118impl Tdx for SecurityList {
119    type Item = [SecurityListData];
120
121    /// market = 0, start = 0 表示深市所有证券从第 0 个开始的 1000 条证券; 共 16 字节。
122    const SEND: &'static [u8] = &[
123        0x0c, 0x01, 0x18, 0x64, 0x01, 0x01, 0x06, 0x00, 0x06, 0x00, 0x50, 0x04, 0x00, 0x00, 0x00,
124        0x00,
125    ];
126    const TAG: &'static str = "股票、指数列表";
127
128    fn send(&mut self) -> &[u8] {
129        &self.send
130    }
131
132    /// 前 2 字节表示列表的长度,剩余字节中,每 29 字节使用 [`SecurityListData::parse`] 解析。
133    fn parse(&mut self, v: Vec<u8>) {
134        self.count = u16_from_le_bytes(&v, 0) as usize;
135        self.data = v[2..]
136            .chunks_exact(29)
137            .map(SecurityListData::parse)
138            .collect();
139        debug_assert_eq!(self.count, self.data.len());
140        self.response = v;
141    }
142
143    fn result(&self) -> &Self::Item {
144        &self.data
145    }
146}
147
148#[test]
149fn connection() -> Result<()> {
150    SecurityList::default().recv_parsed(&mut crate::tcp::Tcp::new()?)?;
151    Ok(())
152}
153
154/// [`SecurityList`] 的解析结果。具体为指数、股票、债券等证券的代码、名称。
155///
156/// ## 注意
157/// 有些响应的字节没有被解析,具体查看 [`SecurityListData::parse`] 的说明。
158#[derive(Debug, Clone, serde::Serialize)]
159pub struct SecurityListData {
160    pub code: String,
161    /// `\u0000` 字符表示空格
162    pub name: String,
163}
164
165impl SecurityListData {
166    /// 解析 [`SecurityList`] 的响应字节。传入长度为 29 字节序列。
167    /// ```python
168    /// (
169    ///     code,            # UTF-8 编码
170    ///     volunit,         # 100
171    ///     name_bytes,      # GBK 编码
172    ///     reversed_bytes1,
173    ///     decimal_point,   # 2
174    ///     pre_close_raw,   # 少许结果与实际数据有出入,故不解析
175    ///     reversed_bytes2,
176    /// ) = struct.unpack("<6sH8s4sBI4s", bytes) # python 表示方式
177    /// ```
178    pub fn parse(bytes: &[u8]) -> Self {
179        let code = unsafe { std::str::from_utf8_unchecked(&bytes[0..6]) }.into();
180        let (name, encoding_used, had_errors) = encoding_rs::GBK.decode(&bytes[8..16]);
181        debug_assert_eq!(encoding_used, encoding_rs::GBK);
182        debug_assert!(!had_errors);
183        // let preclose = crate::tcp::helper::vol_amount(u32_from_le_bytes(b, 21) as i32);
184        Self {
185            code,
186            name: name.into(),
187        }
188    }
189}
190
191pub const PACK1: &[u8] = &[
192    0x0c, 0x02, 0x18, 0x93, 0x00, 0x01, 0x03, 0x00, 0x03, 0x00, 0x0d, 0x00, 0x01,
193];
194pub const PACK2: &[u8] = &[
195    0x0c, 0x02, 0x18, 0x94, 0x00, 0x01, 0x03, 0x00, 0x03, 0x00, 0x0d, 0x00, 0x02,
196];
197pub const PACK3: &[u8] = &[
198    0x0c, 0x03, 0x18, 0x99, 0x00, 0x01, 0x20, 0x00, 0x20, 0x00, 0xdb, 0x0f, 0xd5, 0xd0, 0xc9, 0xcc,
199    0xd6, 0xa4, 0xa8, 0xaf, 0x00, 0x00, 0x00, 0x8f, 0xc2, 0x25, 0x40, 0x13, 0x00, 0x00, 0xd5, 0x00,
200    0xc9, 0xcc, 0xbd, 0xf0, 0xd7, 0xea, 0x00, 0x00, 0x00, 0x02,
201];
202pub const RECV_SIZE: usize = 16;
203
204pub fn send_packs(tcp: &mut super::Tcp, decompress: bool) -> Result<()> {
205    use super::{send_recv, send_recv_decompress};
206    if decompress {
207        send_recv_decompress(tcp, PACK1, "PACK1")?;
208        send_recv_decompress(tcp, PACK2, "PACK2")?;
209        send_recv_decompress(tcp, PACK3, "PACK3")?;
210    } else {
211        send_recv(tcp, PACK1, "PACK1")?;
212        send_recv(tcp, PACK2, "PACK2")?;
213        send_recv(tcp, PACK3, "PACK3")?;
214    }
215    Ok(())
216}