fix: fail closed on missing factor values
This commit is contained in:
@@ -4907,6 +4907,25 @@ mod tests {
|
|||||||
assert!(data.factor_by_symbol_id(date, signal_id).is_none());
|
assert!(data.factor_by_symbol_id(date, signal_id).is_none());
|
||||||
assert!(data.candidate_by_symbol_id(date, signal_id).is_none());
|
assert!(data.candidate_by_symbol_id(date, signal_id).is_none());
|
||||||
assert!(day.candidate(signal_id).is_none());
|
assert!(day.candidate(signal_id).is_none());
|
||||||
|
|
||||||
|
// `get_factor` must use the same symbol-id index as direct snapshot
|
||||||
|
// lookups. Sparse factor rows must not accidentally select another
|
||||||
|
// symbol's row or disappear when the date group contains gaps.
|
||||||
|
let market_cap = data.get_factor("000001.SZ", date, date, "MARKET_CAP");
|
||||||
|
assert_eq!(
|
||||||
|
market_cap
|
||||||
|
.iter()
|
||||||
|
.map(|row| (row.date, row.symbol.as_str(), row.value))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![(date, "000001.SZ", 100.0)]
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
data.get_factor("000300.SH", date, date, "market_cap")
|
||||||
|
.is_empty()
|
||||||
|
);
|
||||||
|
assert!(data
|
||||||
|
.get_factor("999999.SZ", date, date, "market_cap")
|
||||||
|
.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -5077,7 +5077,18 @@ impl PlatformExprStrategy {
|
|||||||
.get_factor(&stock.symbol, start, day.date, field)
|
.get_factor(&stock.symbol, start, day.date, field)
|
||||||
.last()
|
.last()
|
||||||
.map(|row| row.value)
|
.map(|row| row.value)
|
||||||
.unwrap_or(0.0);
|
.ok_or_else(|| {
|
||||||
|
BacktestError::Execution(format!(
|
||||||
|
"missing factor value: field={field}, symbol={}, date={}",
|
||||||
|
stock.symbol, day.date
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
if !value.is_finite() {
|
||||||
|
return Err(BacktestError::Execution(format!(
|
||||||
|
"invalid factor value: field={field}, symbol={}, date={}, value={value:?}",
|
||||||
|
stock.symbol, day.date
|
||||||
|
)));
|
||||||
|
}
|
||||||
Ok(Self::normalized_runtime_number(value))
|
Ok(Self::normalized_runtime_number(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6455,7 +6466,18 @@ impl PlatformExprStrategy {
|
|||||||
.get_factor(&stock.symbol, start, day.date, &field)
|
.get_factor(&stock.symbol, start, day.date, &field)
|
||||||
.last()
|
.last()
|
||||||
.map(|row| row.value)
|
.map(|row| row.value)
|
||||||
.unwrap_or(0.0);
|
.ok_or_else(|| {
|
||||||
|
BacktestError::Execution(format!(
|
||||||
|
"missing factor value: field={field}, symbol={}, date={}",
|
||||||
|
stock.symbol, day.date
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
if !value.is_finite() {
|
||||||
|
return Err(BacktestError::Execution(format!(
|
||||||
|
"invalid factor value: field={field}, symbol={}, date={}, value={value:?}",
|
||||||
|
stock.symbol, day.date
|
||||||
|
)));
|
||||||
|
}
|
||||||
Ok(Self::normalized_runtime_number(value))
|
Ok(Self::normalized_runtime_number(value))
|
||||||
}
|
}
|
||||||
"factor_text" | "get_factor_text" => {
|
"factor_text" | "get_factor_text" => {
|
||||||
@@ -14311,6 +14333,22 @@ mod tests {
|
|||||||
.stock_passes_expr(&ctx, &map_day, &map_stock)
|
.stock_passes_expr(&ctx, &map_day, &map_stock)
|
||||||
.expect("factor map filter")
|
.expect("factor map filter")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let missing_factor_error = map_strategy
|
||||||
|
.resolve_runtime_helper(
|
||||||
|
&ctx,
|
||||||
|
&map_day,
|
||||||
|
Some(&missing_stock),
|
||||||
|
"factor_value",
|
||||||
|
&["\"missing_factor\"".to_string()],
|
||||||
|
)
|
||||||
|
.expect_err("missing factor_value must fail closed");
|
||||||
|
assert!(
|
||||||
|
missing_factor_error
|
||||||
|
.to_string()
|
||||||
|
.contains("missing factor value"),
|
||||||
|
"unexpected error: {missing_factor_error}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user