优化分钟历史窗口读取并移除滚动双口径

This commit is contained in:
boris
2026-08-27 13:14:41 +08:00
parent 45cafa5c96
commit ed126a3630
2 changed files with 239 additions and 21 deletions
@@ -0,0 +1,179 @@
use std::hint::black_box;
use std::time::Instant;
use chrono::{Duration, NaiveDate, NaiveDateTime, NaiveTime};
use fidc_core::{
BenchmarkSnapshot, DailyMarketSnapshot, DataSet, Instrument, IntradayExecutionQuote,
};
const SYMBOL: &str = "000001.SZ";
fn dataset(day_count: usize, bars_per_day: usize) -> (DataSet, Vec<NaiveDate>) {
let start = NaiveDate::from_ymd_opt(2025, 1, 1).expect("valid start date");
let dates = (0..day_count)
.map(|offset| start + Duration::days(offset as i64))
.collect::<Vec<_>>();
let markets = dates
.iter()
.map(|date| DailyMarketSnapshot {
date: *date,
symbol: SYMBOL.to_string(),
timestamp: None,
day_open: 10.0,
open: 10.0,
high: 10.5,
low: 9.5,
close: 10.0,
last_price: 10.0,
bid1: 9.99,
ask1: 10.01,
prev_close: 10.0,
volume: 1_000_000,
minute_volume: 1_000,
bid1_volume: 10_000,
ask1_volume: 10_000,
trading_phase: Some("continuous".to_string()),
paused: false,
upper_limit: 11.0,
lower_limit: 9.0,
price_tick: 0.01,
})
.collect::<Vec<_>>();
let benchmarks = dates
.iter()
.map(|date| BenchmarkSnapshot {
date: *date,
benchmark: "000852.SH".to_string(),
open: 1_000.0,
close: 1_000.0,
prev_close: 1_000.0,
volume: 10_000_000,
})
.collect::<Vec<_>>();
let mut quotes = Vec::with_capacity(day_count * bars_per_day);
for date in &dates {
let session_start = date
.and_hms_opt(9, 30, 0)
.expect("valid session start");
for offset in 0..bars_per_day {
let timestamp = session_start + Duration::minutes(offset as i64);
quotes.push(IntradayExecutionQuote {
date: *date,
symbol: SYMBOL.to_string(),
timestamp,
last_price: 10.0 + offset as f64 / 10_000.0,
bid1: 9.99,
ask1: 10.01,
bid1_volume: 10_000,
ask1_volume: 10_000,
volume_delta: 1_000,
amount_delta: 10_000.0,
trading_phase: Some("continuous".to_string()),
});
}
}
let data = DataSet::from_components_with_actions_and_quotes(
vec![Instrument {
symbol: SYMBOL.to_string(),
name: "平安银行".to_string(),
board: "SZ".to_string(),
round_lot: 100,
listed_at: Some(start - Duration::days(1_000)),
delisted_at: None,
status: "active".to_string(),
}],
markets,
Vec::new(),
Vec::new(),
benchmarks,
Vec::new(),
quotes,
)
.expect("build intraday history dataset");
(data, dates)
}
fn timestamp(date: NaiveDate, time: &str) -> NaiveDateTime {
let time = NaiveTime::parse_from_str(time, "%H:%M:%S").expect("valid time");
date.and_time(time)
}
#[test]
fn intraday_history_is_bounded_by_visibility_and_preserves_order() {
let (data, dates) = dataset(3, 4);
let rows = data.history_intraday_quotes_at(
dates[1],
Some(timestamp(dates[1], "09:32:00")),
SYMBOL,
3,
false,
);
assert_eq!(
rows.iter().map(|row| row.timestamp).collect::<Vec<_>>(),
vec![
timestamp(dates[0], "09:33:00"),
timestamp(dates[1], "09:30:00"),
timestamp(dates[1], "09:31:00"),
]
);
let including_now = data.history_intraday_quotes_at(
dates[1],
Some(timestamp(dates[1], "09:32:00")),
SYMBOL,
3,
true,
);
assert_eq!(
including_now
.iter()
.map(|row| row.timestamp)
.collect::<Vec<_>>(),
vec![
timestamp(dates[1], "09:30:00"),
timestamp(dates[1], "09:31:00"),
timestamp(dates[1], "09:32:00"),
]
);
}
#[test]
#[ignore = "manual release-mode intraday history benchmark"]
fn benchmark_bounded_intraday_history() {
let (data, dates) = dataset(250, 240);
let active_datetime = timestamp(*dates.last().expect("last date"), "13:29:00");
for _ in 0..5 {
black_box(data.history_intraday_quotes_at(
active_datetime.date(),
Some(active_datetime),
SYMBOL,
30,
true,
));
}
let started = Instant::now();
let mut checksum = 0_i64;
for _ in 0..200 {
let rows = data.history_intraday_quotes_at(
active_datetime.date(),
Some(active_datetime),
SYMBOL,
30,
true,
);
checksum += rows
.last()
.expect("history row")
.timestamp
.and_utc()
.timestamp();
black_box(&rows);
}
let elapsed = started.elapsed();
eprintln!(
"intraday_history_benchmark iterations=200 rows_per_dataset=60000 elapsed_seconds={:.6} checksum={checksum}",
elapsed.as_secs_f64(),
);
}