perf(data): normalize owned daily bundles in parallel with stable errors
This commit is contained in:
@@ -1586,48 +1586,15 @@ impl DataSet {
|
|||||||
benchmark_by_date: BTreeMap::new(),
|
benchmark_by_date: BTreeMap::new(),
|
||||||
corporate_actions_by_date: BTreeMap::new(),
|
corporate_actions_by_date: BTreeMap::new(),
|
||||||
};
|
};
|
||||||
for mut bundle in bundles {
|
// Indexed collection retains chronological error precedence while each
|
||||||
|
// worker validates and normalizes only its owned day buffers.
|
||||||
|
let prepared = bundles
|
||||||
|
.into_par_iter()
|
||||||
|
.map(normalize_daily_snapshot_bundle)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for bundle in prepared {
|
||||||
|
let bundle = bundle?;
|
||||||
let date = bundle.date;
|
let date = bundle.date;
|
||||||
if bundle.benchmark.date != date {
|
|
||||||
return Err(DataSetError::InvalidDailyBundleComponentDate {
|
|
||||||
kind: "benchmark",
|
|
||||||
bundle_date: date,
|
|
||||||
row_date: bundle.benchmark.date,
|
|
||||||
symbol: bundle.benchmark.benchmark.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
validate_daily_bundle_component_dates(
|
|
||||||
&bundle.market,
|
|
||||||
date,
|
|
||||||
"market",
|
|
||||||
|row| row.date,
|
|
||||||
|row| row.symbol.as_str(),
|
|
||||||
)?;
|
|
||||||
validate_daily_bundle_component_dates(
|
|
||||||
&bundle.factors,
|
|
||||||
date,
|
|
||||||
"factor",
|
|
||||||
|row| row.date,
|
|
||||||
|row| row.symbol.as_str(),
|
|
||||||
)?;
|
|
||||||
validate_daily_bundle_component_dates(
|
|
||||||
&bundle.candidates,
|
|
||||||
date,
|
|
||||||
"candidate",
|
|
||||||
|row| row.date,
|
|
||||||
|row| row.symbol.as_str(),
|
|
||||||
)?;
|
|
||||||
validate_daily_bundle_component_dates(
|
|
||||||
&bundle.corporate_actions,
|
|
||||||
date,
|
|
||||||
"corporate_action",
|
|
||||||
|row| row.date,
|
|
||||||
|row| row.symbol.as_str(),
|
|
||||||
)?;
|
|
||||||
sort_rows_by_symbol_if_needed(&mut bundle.market, |row| row.symbol.as_str());
|
|
||||||
bundle.factors = normalize_factor_snapshots(bundle.factors)?;
|
|
||||||
sort_rows_by_symbol_if_needed(&mut bundle.factors, |row| row.symbol.as_str());
|
|
||||||
sort_rows_by_symbol_if_needed(&mut bundle.candidates, |row| row.symbol.as_str());
|
|
||||||
if !bundle.market.is_empty() {
|
if !bundle.market.is_empty() {
|
||||||
grouped.market_by_date.insert(date, bundle.market);
|
grouped.market_by_date.insert(date, bundle.market);
|
||||||
}
|
}
|
||||||
@@ -4472,6 +4439,38 @@ fn normalize_history_frequency(frequency: &str) -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_daily_snapshot_bundle(
|
||||||
|
mut bundle: DailySnapshotBundle,
|
||||||
|
) -> Result<DailySnapshotBundle, DataSetError> {
|
||||||
|
let date = bundle.date;
|
||||||
|
if bundle.benchmark.date != date {
|
||||||
|
return Err(DataSetError::InvalidDailyBundleComponentDate {
|
||||||
|
kind: "benchmark",
|
||||||
|
bundle_date: date,
|
||||||
|
row_date: bundle.benchmark.date,
|
||||||
|
symbol: bundle.benchmark.benchmark.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
validate_daily_bundle_component_dates(
|
||||||
|
&bundle.market, date, "market", |row| row.date, |row| row.symbol.as_str(),
|
||||||
|
)?;
|
||||||
|
validate_daily_bundle_component_dates(
|
||||||
|
&bundle.factors, date, "factor", |row| row.date, |row| row.symbol.as_str(),
|
||||||
|
)?;
|
||||||
|
validate_daily_bundle_component_dates(
|
||||||
|
&bundle.candidates, date, "candidate", |row| row.date, |row| row.symbol.as_str(),
|
||||||
|
)?;
|
||||||
|
validate_daily_bundle_component_dates(
|
||||||
|
&bundle.corporate_actions, date, "corporate_action", |row| row.date,
|
||||||
|
|row| row.symbol.as_str(),
|
||||||
|
)?;
|
||||||
|
sort_rows_by_symbol_if_needed(&mut bundle.market, |row| row.symbol.as_str());
|
||||||
|
bundle.factors = normalize_factor_snapshots(bundle.factors)?;
|
||||||
|
sort_rows_by_symbol_if_needed(&mut bundle.factors, |row| row.symbol.as_str());
|
||||||
|
sort_rows_by_symbol_if_needed(&mut bundle.candidates, |row| row.symbol.as_str());
|
||||||
|
Ok(bundle)
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_daily_bundle_component_dates<T, D, S>(
|
fn validate_daily_bundle_component_dates<T, D, S>(
|
||||||
rows: &[T],
|
rows: &[T],
|
||||||
bundle_date: NaiveDate,
|
bundle_date: NaiveDate,
|
||||||
@@ -5542,6 +5541,39 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parallel_daily_bundle_validation_keeps_earliest_error_and_component_order() {
|
||||||
|
let bundles = || (2..30).rev().map(|day| {
|
||||||
|
let date = NaiveDate::from_ymd_opt(2025, 1, day).unwrap();
|
||||||
|
let mut benchmark = benchmark_row("2025-01-01", 20.0);
|
||||||
|
benchmark.date = date;
|
||||||
|
DailySnapshotBundle {
|
||||||
|
date, benchmark,
|
||||||
|
market: vec![market_row("2025-01-01", 10.0, 100)],
|
||||||
|
factors: Vec::new(), candidates: Vec::new(), corporate_actions: Vec::new(),
|
||||||
|
}
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
for threads in [1, 2, 8] {
|
||||||
|
let pool = rayon::ThreadPoolBuilder::new().num_threads(threads).build().unwrap();
|
||||||
|
for _ in 0..4 {
|
||||||
|
let result = pool.install(|| DataSet::from_daily_bundles_with_execution_quotes(
|
||||||
|
Vec::new(), bundles(), Vec::new(),
|
||||||
|
));
|
||||||
|
assert!(matches!(result, Err(DataSetError::InvalidDailyBundleComponentDate {
|
||||||
|
kind: "market", bundle_date, ..
|
||||||
|
}) if bundle_date == NaiveDate::from_ymd_opt(2025, 1, 2).unwrap()));
|
||||||
|
}
|
||||||
|
let mut values = bundles();
|
||||||
|
values.last_mut().unwrap().benchmark.date = NaiveDate::from_ymd_opt(2025, 1, 1).unwrap();
|
||||||
|
let result = pool.install(|| DataSet::from_daily_bundles_with_execution_quotes(
|
||||||
|
Vec::new(), values, Vec::new(),
|
||||||
|
));
|
||||||
|
assert!(matches!(result, Err(DataSetError::InvalidDailyBundleComponentDate {
|
||||||
|
kind: "benchmark", bundle_date, ..
|
||||||
|
}) if bundle_date == NaiveDate::from_ymd_opt(2025, 1, 2).unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn direct_symbol_id_snapshot_lookups_preserve_alignment_for_sparse_rows() {
|
fn direct_symbol_id_snapshot_lookups_preserve_alignment_for_sparse_rows() {
|
||||||
let date = NaiveDate::parse_from_str("2025-01-02", "%Y-%m-%d").unwrap();
|
let date = NaiveDate::parse_from_str("2025-01-02", "%Y-%m-%d").unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user