保留极小非零股息并执行相同权益日期门禁
This commit is contained in:
@@ -15,7 +15,7 @@ pub(crate) fn validate_action<'a>(
|
||||
action.symbol, action.date
|
||||
)
|
||||
})?;
|
||||
if action.share_cash.abs() > f64::EPSILON {
|
||||
if action.share_cash != 0.0 {
|
||||
action.required_cash_payment_date()?;
|
||||
action.required_record_date()?;
|
||||
if action.share_cash < 0.0 { return Err("corporate_action_negative_cash_dividend".into()); }
|
||||
@@ -45,7 +45,7 @@ pub(crate) fn apply(
|
||||
let actions = data.corporate_actions_on(date);
|
||||
for action in actions {
|
||||
validate_action(action, data).map_err(BacktestError::Execution)?;
|
||||
if cash_dividends_enabled && action.share_cash.abs() > f64::EPSILON {
|
||||
if cash_dividends_enabled && action.share_cash != 0.0 {
|
||||
portfolio.registered_quantity(action.required_record_date().map_err(BacktestError::Execution)?, &action.symbol)
|
||||
.map_err(BacktestError::Execution)?;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ pub(crate) fn apply(
|
||||
&& portfolio
|
||||
.position(&action.symbol)
|
||||
.is_some_and(|position| position.quantity > 0)
|
||||
|| (cash_dividends_enabled && action.share_cash.abs() > f64::EPSILON
|
||||
|| (cash_dividends_enabled && action.share_cash != 0.0
|
||||
&& portfolio.registered_quantity(action.record_date.expect("validated record date"), &action.symbol).expect("validated closing fact") > 0)
|
||||
}) {
|
||||
return Ok(BrokerExecutionReport::default());
|
||||
@@ -91,7 +91,7 @@ fn apply_inner(
|
||||
if !action.has_effect() {
|
||||
continue;
|
||||
}
|
||||
if cash_dividends_enabled && action.share_cash.abs() > f64::EPSILON {
|
||||
if cash_dividends_enabled && action.share_cash != 0.0 {
|
||||
let record_date = action.required_record_date().map_err(BacktestError::Execution)?;
|
||||
let entitled_quantity = portfolio.registered_quantity(record_date, &action.symbol).map_err(BacktestError::Execution)?;
|
||||
let cash_before = portfolio.cash();
|
||||
@@ -757,6 +757,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiny_known_cash_rate_is_preserved_and_cannot_waive_payment_or_record_evidence() {
|
||||
let rate = 1e-16;
|
||||
for payment in [None, Some(date())] {
|
||||
let mut action = dividend(payment);
|
||||
action.share_cash = rate;
|
||||
assert!(action.has_effect());
|
||||
let data = conversion_data(vec![action], false);
|
||||
if payment.is_none() {
|
||||
assert!(data.get_dividend("000001.SZ", date(), date()).is_err());
|
||||
let mut book = conversion_book();
|
||||
let before = book.financial_replay_identity();
|
||||
assert!(apply(date(), &data, &mut book, &mut Vec::new(), true, true).is_err());
|
||||
assert_eq!(book.financial_replay_identity(), before);
|
||||
} else {
|
||||
let observed = data.get_dividend("000001.SZ", date(), date()).unwrap();
|
||||
assert_eq!(observed.len(), 1);
|
||||
assert_eq!(observed[0].dividend_cash_before_tax, rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicitly_delayed_dividend_remains_receivable_until_actual_payment() {
|
||||
let payment = date().succ_opt().unwrap().succ_opt().unwrap();
|
||||
|
||||
@@ -468,7 +468,7 @@ impl CorporateAction {
|
||||
}
|
||||
|
||||
pub fn has_effect(&self) -> bool {
|
||||
self.share_cash.abs() > f64::EPSILON
|
||||
self.share_cash != 0.0
|
||||
|| (self.split_ratio() - 1.0).abs() > f64::EPSILON
|
||||
|| self.issue_quantity.abs() > f64::EPSILON
|
||||
|| self.reform
|
||||
@@ -2909,7 +2909,7 @@ impl DataSet {
|
||||
.corporate_actions_by_date
|
||||
.range(start..=end)
|
||||
.flat_map(|(_, actions)| actions.iter())
|
||||
.filter(|action| action.symbol == symbol && (!action.share_cash.is_finite() || action.share_cash.abs() > f64::EPSILON))
|
||||
.filter(|action| action.symbol == symbol && action.share_cash != 0.0)
|
||||
.map(|action| {
|
||||
crate::finite_serialization::validate(action).map_err(|error| BacktestError::Execution(format!(
|
||||
"corporate_action_invalid_number: symbol={} ex_date={} detail={error}", action.symbol, action.date)))?;
|
||||
|
||||
Reference in New Issue
Block a user