perf: build calendar series boundaries in one pass
This commit is contained in:
@@ -1397,9 +1397,12 @@ impl DataSet {
|
|||||||
let mut calendar_dates = self.calendar.days().to_vec();
|
let mut calendar_dates = self.calendar.days().to_vec();
|
||||||
calendar_dates.extend(dates);
|
calendar_dates.extend(dates);
|
||||||
let calendar = Arc::new(TradingCalendar::new(calendar_dates));
|
let calendar = Arc::new(TradingCalendar::new(calendar_dates));
|
||||||
self.market_series_end_positions_by_calendar_index = Arc::new(
|
self.market_series_end_positions_by_calendar_index =
|
||||||
build_calendar_series_end_positions(&self.market_series_by_symbol_id, &calendar),
|
Arc::new(build_calendar_series_end_positions(
|
||||||
);
|
&self.market_series_by_symbol_id,
|
||||||
|
&self.market_symbol_ids_by_date,
|
||||||
|
&calendar,
|
||||||
|
));
|
||||||
self.calendar = calendar;
|
self.calendar = calendar;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -1821,8 +1824,11 @@ impl DataSet {
|
|||||||
adjusted_close_series_by_symbol_id[symbol_id as usize] = Some(Arc::clone(series));
|
adjusted_close_series_by_symbol_id[symbol_id as usize] = Some(Arc::clone(series));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let market_series_end_positions_by_calendar_index =
|
let market_series_end_positions_by_calendar_index = build_calendar_series_end_positions(
|
||||||
build_calendar_series_end_positions(&market_series_by_symbol_id, &calendar);
|
&market_series_by_symbol_id,
|
||||||
|
&market_symbol_ids_by_date,
|
||||||
|
&calendar,
|
||||||
|
);
|
||||||
let execution_quotes_by_date = build_execution_quote_index(execution_quotes);
|
let execution_quotes_by_date = build_execution_quote_index(execution_quotes);
|
||||||
let mut execution_quote_dates =
|
let mut execution_quote_dates =
|
||||||
execution_quotes_by_date.keys().copied().collect::<Vec<_>>();
|
execution_quotes_by_date.keys().copied().collect::<Vec<_>>();
|
||||||
@@ -4326,6 +4332,7 @@ fn build_dense_row_positions<T>(
|
|||||||
|
|
||||||
fn build_calendar_series_end_positions(
|
fn build_calendar_series_end_positions(
|
||||||
series_by_symbol_id: &[Option<Arc<SymbolPriceSeries>>],
|
series_by_symbol_id: &[Option<Arc<SymbolPriceSeries>>],
|
||||||
|
market_symbol_ids_by_date: &BTreeMap<NaiveDate, Vec<u32>>,
|
||||||
calendar: &TradingCalendar,
|
calendar: &TradingCalendar,
|
||||||
) -> Option<CalendarSeriesEndPositions> {
|
) -> Option<CalendarSeriesEndPositions> {
|
||||||
let entries = series_by_symbol_id.len().checked_mul(calendar.len())?;
|
let entries = series_by_symbol_id.len().checked_mul(calendar.len())?;
|
||||||
@@ -4340,54 +4347,65 @@ fn build_calendar_series_end_positions(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let calendar_days = calendar.days();
|
let mut completed_rows_by_symbol = series_by_symbol_id
|
||||||
let positions_by_symbol = series_by_symbol_id
|
.iter()
|
||||||
.par_iter()
|
|
||||||
.map(|series| {
|
.map(|series| {
|
||||||
let series = series.as_deref()?;
|
if series.is_some() {
|
||||||
let mut decision = Vec::with_capacity(calendar_days.len());
|
0
|
||||||
let mut current = Vec::with_capacity(calendar_days.len());
|
} else {
|
||||||
let mut series_index = 0usize;
|
MISSING_ROW_POSITION
|
||||||
for date in calendar_days {
|
|
||||||
while series
|
|
||||||
.dates
|
|
||||||
.get(series_index)
|
|
||||||
.is_some_and(|series_date| *series_date < *date)
|
|
||||||
{
|
|
||||||
series_index += 1;
|
|
||||||
}
|
|
||||||
decision.push(series_index as u32);
|
|
||||||
let current_index = if series.dates.get(series_index) == Some(date) {
|
|
||||||
series_index + 1
|
|
||||||
} else {
|
|
||||||
series_index
|
|
||||||
};
|
|
||||||
current.push(current_index as u32);
|
|
||||||
}
|
}
|
||||||
Some((decision, current))
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let positions_by_calendar = (0..calendar_days.len())
|
let mut market_dates = market_symbol_ids_by_date.iter().peekable();
|
||||||
.into_par_iter()
|
let mut decision = Vec::with_capacity(calendar.len());
|
||||||
.map(|calendar_index| {
|
let mut current = Vec::with_capacity(calendar.len());
|
||||||
let mut decision = Vec::with_capacity(series_by_symbol_id.len());
|
|
||||||
let mut current = Vec::with_capacity(series_by_symbol_id.len());
|
for date in calendar.days() {
|
||||||
for positions in &positions_by_symbol {
|
while market_dates
|
||||||
if let Some((symbol_decision, symbol_current)) = positions {
|
.peek()
|
||||||
decision.push(symbol_decision[calendar_index]);
|
.is_some_and(|(market_date, _)| *market_date < date)
|
||||||
current.push(symbol_current[calendar_index]);
|
{
|
||||||
} else {
|
let (_, symbol_ids) = market_dates.next()?;
|
||||||
decision.push(MISSING_ROW_POSITION);
|
advance_completed_series_rows(&mut completed_rows_by_symbol, symbol_ids)?;
|
||||||
current.push(MISSING_ROW_POSITION);
|
}
|
||||||
|
|
||||||
|
decision.push(completed_rows_by_symbol.clone());
|
||||||
|
let mut current_on_date = completed_rows_by_symbol.clone();
|
||||||
|
if market_dates
|
||||||
|
.peek()
|
||||||
|
.is_some_and(|(market_date, _)| *market_date == date)
|
||||||
|
{
|
||||||
|
let (_, symbol_ids) = market_dates.next()?;
|
||||||
|
for symbol_id in symbol_ids {
|
||||||
|
let end = current_on_date.get_mut(*symbol_id as usize)?;
|
||||||
|
if *end == MISSING_ROW_POSITION {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
*end = completed_rows_by_symbol[*symbol_id as usize].checked_add(1)?;
|
||||||
}
|
}
|
||||||
(decision, current)
|
advance_completed_series_rows(&mut completed_rows_by_symbol, symbol_ids)?;
|
||||||
})
|
}
|
||||||
.collect::<Vec<_>>();
|
current.push(current_on_date);
|
||||||
let (decision, current) = positions_by_calendar.into_iter().unzip();
|
}
|
||||||
|
|
||||||
Some(CalendarSeriesEndPositions { decision, current })
|
Some(CalendarSeriesEndPositions { decision, current })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn advance_completed_series_rows(
|
||||||
|
completed_rows_by_symbol: &mut [u32],
|
||||||
|
symbol_ids: &[u32],
|
||||||
|
) -> Option<()> {
|
||||||
|
for symbol_id in symbol_ids {
|
||||||
|
let completed = completed_rows_by_symbol.get_mut(*symbol_id as usize)?;
|
||||||
|
if *completed == MISSING_ROW_POSITION {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
*completed = completed.checked_add(1)?;
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
fn dense_row_position(
|
fn dense_row_position(
|
||||||
positions_by_date: &Option<DenseRowPositionIndex>,
|
positions_by_date: &Option<DenseRowPositionIndex>,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
@@ -6110,6 +6128,37 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn series_end_position_index_preserves_sparse_and_duplicate_date_boundaries() {
|
||||||
|
let first = NaiveDate::from_ymd_opt(2025, 1, 2).unwrap();
|
||||||
|
let duplicate_date = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap();
|
||||||
|
let last = NaiveDate::from_ymd_opt(2025, 1, 6).unwrap();
|
||||||
|
let rows = vec![
|
||||||
|
market_row("2025-01-03", 10.0, 100),
|
||||||
|
market_row("2025-01-03", 10.1, 110),
|
||||||
|
market_row("2025-01-06", 10.2, 120),
|
||||||
|
];
|
||||||
|
let series = Arc::new(SymbolPriceSeries::new("000001.SZ".to_string(), rows.iter()));
|
||||||
|
let series_by_symbol_id = vec![Some(series), None];
|
||||||
|
let market_symbol_ids_by_date =
|
||||||
|
BTreeMap::from([(duplicate_date, vec![0, 0]), (last, vec![0])]);
|
||||||
|
let calendar = TradingCalendar::new(vec![first, duplicate_date, last]);
|
||||||
|
|
||||||
|
let positions = build_calendar_series_end_positions(
|
||||||
|
&series_by_symbol_id,
|
||||||
|
&market_symbol_ids_by_date,
|
||||||
|
&calendar,
|
||||||
|
)
|
||||||
|
.expect("series position index");
|
||||||
|
|
||||||
|
assert_eq!(positions.decision[0], vec![0, MISSING_ROW_POSITION]);
|
||||||
|
assert_eq!(positions.current[0], vec![0, MISSING_ROW_POSITION]);
|
||||||
|
assert_eq!(positions.decision[1], vec![0, MISSING_ROW_POSITION]);
|
||||||
|
assert_eq!(positions.current[1], vec![1, MISSING_ROW_POSITION]);
|
||||||
|
assert_eq!(positions.decision[2], vec![2, MISSING_ROW_POSITION]);
|
||||||
|
assert_eq!(positions.current[2], vec![3, MISSING_ROW_POSITION]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn source_volume_contract_rejects_windows_containing_missing_values() {
|
fn source_volume_contract_rejects_windows_containing_missing_values() {
|
||||||
let data = volume_contract_data(Some([1.0, 0.0, 1.0]));
|
let data = volume_contract_data(Some([1.0, 0.0, 1.0]));
|
||||||
|
|||||||
Reference in New Issue
Block a user