use crate::bytes_helper::u16_from_le_bytes;
use crate::tcp::Tdx;
#[derive(Debug, Clone)]
pub struct HistoryTransaction<'d> {
pub send: Box<[u8]>,
pub market: u16,
pub code: &'d str,
pub start: u16,
pub count: u16,
pub date: u32,
pub response: Vec<u8>,
pub data: Vec<super::transaction::TransactionData>,
}
impl<'d> HistoryTransaction<'d> {
pub fn new(market: u16, code: &'d str, start: u16, count: u16, date: u32) -> Self {
assert_eq!(code.len(), 6, "股票代码必须是6位");
let mut send = [0u8; Self::LEN];
send[0..12].copy_from_slice(&Self::SEND[0..12]);
send[12..16].copy_from_slice(&date.to_le_bytes());
send[16..18].copy_from_slice(&market.to_le_bytes());
send[18..24].copy_from_slice(code.as_bytes());
send[24..26].copy_from_slice(&start.to_le_bytes());
send[26..28].copy_from_slice(&count.to_le_bytes());
Self {
send: send.into(),
market,
code,
start,
count,
date,
response: Vec::new(),
data: Vec::new(),
}
}
}
impl<'a> Tdx for HistoryTransaction<'a> {
type Item = [super::transaction::TransactionData];
const SEND: &'static [u8] = &[
0x0c, 0x01, 0x30, 0x01, 0x00, 0x01, 0x12, 0x00, 0x12, 0x00, 0xb5,
0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x00, 0x00, 0x00,
0x00,
];
const TAG: &'static str = "历史逐笔成交";
const LEN: usize = 12 + 4 + 2 + 6 + 2 + 2;
fn send(&mut self) -> &[u8] {
&self.send
}
fn parse(&mut self, v: Vec<u8>) {
self.data = Vec::new();
if v.len() < 6 {
self.response = v;
return;
}
let num_ticks = u16_from_le_bytes(&v, 0);
let mut pos = 6;
let mut last_price = 0i32;
for _ in 0..num_ticks {
if v.len() - pos < 3 {
break;
}
let time_minutes = u16_from_le_bytes(&v, pos);
pos += 2;
let hour = time_minutes / 60;
let minute = time_minutes % 60;
let Some(price_raw) = super::minute_time::price_checked(&v, &mut pos) else {
self.response = v;
return;
};
let Some(vol) = super::minute_time::price_checked(&v, &mut pos) else {
self.response = v;
return;
};
let Some(buyorsell) = super::minute_time::price_checked(&v, &mut pos) else {
self.response = v;
return;
};
let Some(_reserved) = super::minute_time::price_checked(&v, &mut pos) else {
self.response = v;
return;
};
last_price += price_raw;
let price = last_price as f64 / 100.0;
self.data.push(super::transaction::TransactionData {
time: format!("{hour:02}:{minute:02}"),
price,
vol,
num: 0, buyorsell,
});
}
let ticks_complete = self.data.len() == num_ticks as usize;
let bytes_consumed = ticks_complete && pos + 8 >= v.len();
let prices_valid = self
.data
.iter()
.all(|t| (0.01..=100000.0).contains(&t.price));
if !ticks_complete || !bytes_consumed || !prices_valid {
self.data = Vec::new();
}
self.response = v;
}
fn result(&self) -> &Self::Item {
&self.data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let tx = HistoryTransaction::new(0, "000001", 20, 50, 20260901);
assert_eq!(tx.market, 0);
assert_eq!(tx.code, "000001");
assert_eq!(tx.start, 20);
assert_eq!(tx.count, 50);
assert_eq!(tx.date, 20260901);
assert_eq!(tx.send.len(), 28);
assert_eq!(&tx.send[12..16], &20260901u32.to_le_bytes());
assert_eq!(&tx.send[16..18], &0u16.to_le_bytes());
assert_eq!(&tx.send[18..24], b"000001");
assert_eq!(&tx.send[24..26], &20u16.to_le_bytes());
assert_eq!(&tx.send[26..28], &50u16.to_le_bytes());
}
#[test]
#[should_panic(expected = "股票代码必须是6位")]
fn test_invalid_code() {
HistoryTransaction::new(0, "00001", 0, 20, 20260901);
}
#[test]
fn parse_two_ticks() {
let mut v = vec![0x02, 0x00, 0x00, 0x00, 0x00, 0x00];
v.extend_from_slice(&[0x3a, 0x02]); v.extend_from_slice(&[0x84, 0x0e, 0xa4, 0x01, 0x00, 0x00]); v.extend_from_slice(&[0x3b, 0x02]);
v.extend_from_slice(&[0x32, 0x90, 0x01, 0x01, 0x00]); let mut tx = HistoryTransaction::new(0, "000001", 0, 10, 20260901);
tx.parse(v);
assert_eq!(tx.result().len(), 2);
assert_eq!(tx.result()[0].time, "09:30");
assert_eq!(tx.result()[0].price, 9.0);
assert_eq!(tx.result()[0].vol, 100);
assert_eq!(tx.result()[0].buyorsell, 0);
assert_eq!(tx.result()[1].time, "09:31");
assert_eq!(tx.result()[1].price, 9.5);
assert_eq!(tx.result()[1].buyorsell, 1);
}
}