use crate::tcp::{helper::price, Tdx};
use crate::bytes_helper::u16_from_le_bytes;
#[derive(Debug, Clone)]
pub struct Transaction<'d> {
pub send: Box<[u8]>,
pub market: u16,
pub code: &'d str,
pub start: u16,
pub count: u16,
pub response: Vec<u8>,
pub data: Vec<TransactionData>,
}
impl<'d> Transaction<'d> {
pub fn new(market: u16, code: &'d str, start: u16, count: u16) -> Self {
assert_eq!(code.len(), 6, "股票代码必须是6位");
let mut send = [0u8; Self::LEN];
send[0..12].copy_from_slice(Self::SEND);
send[12..14].copy_from_slice(&market.to_le_bytes());
send[14..20].copy_from_slice(code.as_bytes());
send[20..22].copy_from_slice(&start.to_le_bytes());
send[22..24].copy_from_slice(&count.to_le_bytes());
Self {
send: send.into(),
market,
code,
start,
count,
response: Vec::new(),
data: Vec::new(),
}
}
}
impl<'a> Tdx for Transaction<'a> {
type Item = [TransactionData];
const SEND: &'static [u8] = &[
0x0c, 0x17, 0x08, 0x01, 0x01, 0x01, 0x0e, 0x00, 0x0e, 0x00, 0xc5,
0x0f, ];
const TAG: &'static str = "成交明细";
const LEN: usize = 12 + 2 + 6 + 2 + 2;
fn send(&mut self) -> &[u8] {
&self.send
}
fn parse(&mut self, v: Vec<u8>) {
let mut pos = 0;
let num_ticks = u16_from_le_bytes(&v, pos);
pos += 2;
self.data = Vec::with_capacity(num_ticks as usize);
let mut last_price = 0i32;
for _ in 0..num_ticks {
let time_minutes = u16_from_le_bytes(&v, pos);
pos += 2;
let hour = time_minutes / 60;
let minute = time_minutes % 60;
let price_raw = price(&v, &mut pos);
let vol = price(&v, &mut pos);
let num = price(&v, &mut pos);
let buyorsell = price(&v, &mut pos);
let _reserved = price(&v, &mut pos);
last_price += price_raw;
let price = last_price as f64 / 100.0;
self.data.push(TransactionData {
time: format!("{:02}:{:02}", hour, minute),
price,
vol,
num,
buyorsell,
});
}
self.response = v;
}
fn result(&self) -> &Self::Item {
&self.data
}
}
#[derive(Debug, Default, Clone, serde::Serialize)]
pub struct TransactionData {
pub time: String,
pub price: f64,
pub vol: i32,
pub num: i32,
pub buyorsell: i32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_new() {
let transaction = Transaction::new(0, "000001", 0, 20);
assert_eq!(transaction.market, 0);
assert_eq!(transaction.code, "000001");
assert_eq!(transaction.start, 0);
assert_eq!(transaction.count, 20);
assert_eq!(transaction.send.len(), 24);
}
#[test]
fn test_transaction_new_with_start() {
let transaction = Transaction::new(1, "600000", 20, 50);
assert_eq!(transaction.market, 1);
assert_eq!(transaction.code, "600000");
assert_eq!(transaction.start, 20);
assert_eq!(transaction.count, 50);
}
#[test]
fn test_transaction_send_bytes() {
let transaction = Transaction::new(0, "000001", 0, 20);
assert_eq!(&transaction.send[0..12], &[0x0c, 0x17, 0x08, 0x01, 0x01, 0x01, 0x0e, 0x00, 0x0e, 0x00, 0xc5, 0x0f]);
assert_eq!(&transaction.send[12..14], &[0x00, 0x00]);
assert_eq!(&transaction.send[14..20], b"000001");
assert_eq!(&transaction.send[20..22], &[0x00, 0x00]);
assert_eq!(&transaction.send[22..24], &[0x14, 0x00]);
}
#[test]
#[should_panic(expected = "股票代码必须是6位")]
fn test_transaction_invalid_code() {
Transaction::new(0, "00001", 0, 20);
}
#[test]
fn test_connection() {
if std::env::var("RUSTDX_SKIP_INTEGRATION_TESTS").is_ok() {
println!("⚠️ 跳过集成测试 (RUSTDX_SKIP_INTEGRATION_TESTS 已设置)");
return;
}
println!("⚠️ 集成测试需要手动验证(需要实际TCP连接)");
}
}