Skip to main content

wmjtyd_libstock/data/
kline.rs

1//! The kline-related operations.
2
3use std::io::{BufRead, BufReader};
4
5use crypto_msg_parser::KlineMsg;
6use rust_decimal::prelude::ToPrimitive;
7
8use super::{
9    fields::{
10        ExchangeTimestampRepr, ExchangeTypeRepr, MarketTypeRepr, MessageTypeRepr, PeriodRepr,
11        ReadExt, ReceivedTimestampRepr, StructureError, SymbolPairRepr,
12    },
13    hex::{HexDataError, NumToBytesExt},
14};
15
16/// The size of the k-line indicators.
17///
18/// `[open, high, low, close, volume]`
19const KLINE_INDICATOR_SIZE: [usize; 5] = [5, 5, 5, 5, 10];
20
21/// Get the ordered fixed array with k-line indicators.
22///
23/// The indicators will be ordered in `[open, high, low, close, volume]`.
24fn get_kline_indi_array(kline: &KlineMsg) -> [f64; 5] {
25    [kline.open, kline.high, kline.low, kline.close, kline.volume]
26}
27
28/// Encode a [`KlineMsg`] to bytes.
29pub fn encode_kline(kline: &KlineMsg) -> KlineResult<Vec<u8>> {
30    // This data should have 47 bytes.
31    let mut bytes = Vec::<u8>::with_capacity(47);
32
33    // 1. 交易所时间戳: 6 字节
34    bytes.extend_from_slice(&ExchangeTimestampRepr(kline.timestamp).to_bytes());
35
36    // 2. 收到时间戳: 6 字节
37    bytes.extend_from_slice(&ReceivedTimestampRepr::try_new_from_now()?.to_bytes());
38
39    // 3. EXCHANGE: 1 字节
40    bytes.extend_from_slice(&ExchangeTypeRepr::try_from_str(&kline.exchange)?.to_bytes());
41
42    // 4. MARKET_TYPE: 1 字节信息标识
43    bytes.extend_from_slice(&MarketTypeRepr(kline.market_type).to_bytes());
44
45    // 5. MESSAGE_TYPE: 1 字节信息标识
46    bytes.extend_from_slice(&MessageTypeRepr(kline.msg_type).to_bytes());
47
48    // 6. SYMBOL: 2 字节信息标识
49    bytes.extend_from_slice(&SymbolPairRepr::from_pair(&kline.pair).to_bytes());
50
51    // 7. PERIOD: 1 字节信息标识
52    bytes.extend_from_slice(&PeriodRepr(kline.period.as_str()).try_to_bytes()?);
53
54    // 8. 五個指標 (open (5B), high (5B), low (5B), close (5B)、volume (10B))
55    for (idx, price) in get_kline_indi_array(kline).iter().enumerate() {
56        // FIXME: this code is too ugly!!
57        macro_rules! create_extend_branch {
58            ($ty:ty) => {
59                bytes.extend_from_slice(&<$ty>::encode_bytes(&price.to_string())?)
60            };
61        }
62
63        let size = KLINE_INDICATOR_SIZE[idx];
64
65        match size {
66            5 => create_extend_branch!(u32),
67            10 => create_extend_branch!(u64),
68            _ => unreachable!(),
69        };
70    }
71
72    Ok(bytes)
73}
74
75/// Decode the specified bytes to a [`KlineMsg`].
76pub fn decode_kline(payload: &[u8]) -> KlineResult<KlineMsg> {
77    let mut reader = BufReader::new(payload);
78
79    // 1. 交易所时间戳: 6 字节时间戳
80    let exchange_timestamp = ExchangeTimestampRepr::try_from_reader(&mut reader)?.0;
81
82    // 2. 收到时间戳: 6 字节时间戳 (NOT USED)
83    reader.consume(8);
84    // let received_timestamp = ReceivedTimestampRepr::try_from_reader(&mut reader)?;
85
86    // 3. EXCHANGE: 1 字节信息标识
87    let exchange_type = ExchangeTypeRepr::try_from_reader(&mut reader)?.0;
88
89    // 4. MARKET_TYPE: 1 字节信息标识
90    let market_type = MarketTypeRepr::try_from_reader(&mut reader)?.0;
91
92    // 5. MESSAGE_TYPE: 1 字节信息标识
93    let msg_type = MessageTypeRepr::try_from_reader(&mut reader)?.0;
94
95    // 6. SYMBOL_PAIR: 2 字节信息标识
96    let SymbolPairRepr(symbol, pair) = SymbolPairRepr::try_from_reader(&mut reader)?;
97
98    // 7. PERIOD: 1 字节信息标识
99    let period = PeriodRepr::try_from_reader(&mut reader)?.0;
100
101    // 8. 五個指標 (open (5B), high (5B), low (5B), close (5B)、volume (10B))
102    let mut indicators = [0.0f64; 5];
103    for (idx, size) in KLINE_INDICATOR_SIZE.iter().enumerate() {
104        // FIXME: this code is too ugly!!
105        macro_rules! get_indicators_branch {
106            ($ty: ty) => {{
107                let raw = reader.read_exact_array()?;
108                let indicator = <$ty>::decode_bytes(&raw)
109                    .to_f64()
110                    .ok_or_else(|| KlineError::DecimalConvertF64Failed(raw.to_vec()))?;
111
112                indicator
113            }};
114        }
115
116        indicators[idx] = match *size {
117            5 => get_indicators_branch!(u32),
118            10 => get_indicators_branch!(u64),
119            _ => unreachable!(),
120        };
121    }
122    let [open, high, low, close, volume] = indicators;
123
124    Ok(KlineMsg {
125        exchange: exchange_type.to_string(),
126        market_type,
127        msg_type,
128        pair: pair.to_string(),
129        symbol: symbol.to_string(),
130        timestamp: exchange_timestamp as i64,
131        open,
132        high,
133        low,
134        close,
135        /// base volume
136        volume,
137        /// m, minute; H, hour; D, day; W, week; M, month; Y, year
138        period: period.to_string(),
139        /// quote volume
140        quote_volume: None,
141        json: String::new(),
142    })
143}
144
145#[derive(thiserror::Error, Debug)]
146pub enum KlineError {
147    #[error("data/hex error: {0}")]
148    HexDataError(#[from] HexDataError),
149
150    #[error("structure error: {0}")]
151    StructureError(#[from] StructureError),
152
153    #[error("failed to convert the following bytes to f64: {0:?}")]
154    DecimalConvertF64Failed(Vec<u8>),
155}
156
157pub type KlineResult<T> = Result<T, KlineError>;