严格校验换股证据并原子提交公司行为批次

This commit is contained in:
boris
2026-09-14 22:57:53 +08:00
parent d4e7cdd5b5
commit 59a0c95aae
5 changed files with 386 additions and 34 deletions
+21 -11
View File
@@ -459,19 +459,29 @@ impl CorporateAction {
self.successor_symbol
.as_ref()
.is_some_and(|symbol| !symbol.trim().is_empty())
&& self.successor_ratio_value() > 0.0
}
pub fn successor_ratio_value(&self) -> f64 {
self.successor_ratio
.filter(|ratio| ratio.is_finite() && *ratio > 0.0)
.unwrap_or(1.0)
}
pub fn successor_cash_value(&self) -> f64 {
self.successor_cash
.filter(|cash| cash.is_finite())
.unwrap_or(0.0)
/// A code mapping alone is not evidence for a 1:1 financial conversion.
/// An absent cash component means no declared cash leg; an invalid one
/// must never be replaced with zero.
pub(crate) fn validated_successor_terms(&self) -> Result<Option<(&str, f64, f64)>, String> {
let fail = |reason: &str| format!(
"corporate_action_{reason}: symbol={} action_date={}", self.symbol, self.date);
let Some(symbol) = self.successor_symbol.as_deref() else {
if self.successor_ratio.is_some() || self.successor_cash.is_some() {
return Err(fail("successor_symbol_missing"));
}
return Ok(None);
};
if symbol.is_empty() || symbol.trim() != symbol || symbol == self.symbol
|| self.symbol.is_empty() || self.symbol.trim() != self.symbol {
return Err(fail("successor_symbol_invalid"));
}
let ratio = self.successor_ratio.filter(|ratio| ratio.is_finite() && *ratio > 0.)
.ok_or_else(|| fail("successor_ratio_missing_or_invalid"))?;
let cash = self.successor_cash.unwrap_or(0.);
if !cash.is_finite() { return Err(fail("successor_cash_invalid")); }
Ok(Some((symbol, ratio, cash)))
}
}