将已确认应收股息计入权益并验证支付资产守恒

This commit is contained in:
boris
2026-10-05 02:30:38 +08:00
parent cd82492a89
commit af655de0c1
2 changed files with 34 additions and 1 deletions
+22
View File
@@ -765,16 +765,38 @@ mod tests {
assert_eq!(records[0].payable_date, payment);
let mut book = conversion_book();
let mut notes = Vec::new();
let equity_before = book.total_equity();
apply(date(), &data, &mut book, &mut notes, true, true).unwrap();
assert_eq!(book.cash(), 1000.);
assert_eq!(book.cash_receivables().len(), 1);
assert_eq!(book.cash_receivables()[0].amount, 50.);
assert_eq!(book.cash_receivable_value(), 50.);
assert_eq!(book.total_equity(), equity_before);
assert_eq!(book.unit_net_value(), equity_before / book.units());
settle_receivables(date().succ_opt().unwrap(), &data, &mut book, &mut notes, false, None).unwrap();
assert_eq!(book.cash(), 1000.);
assert_eq!(book.cash_receivables().len(), 1);
assert_eq!(book.total_equity(), equity_before);
settle_receivables(payment, &data, &mut book, &mut notes, false, None).unwrap();
assert_eq!(book.cash(), 1050.);
assert!(book.cash_receivables().is_empty());
assert_eq!(book.cash_receivable_value(), 0.);
assert_eq!(book.total_equity(), equity_before);
}
#[test]
fn dividend_receivable_survives_a_flat_position_without_becoming_spendable_cash() {
let mut account = PortfolioState::new(1000.);
account.add_cash_receivable(CashReceivable { symbol: "000001.SZ".into(),
ex_date: date(), payable_date: date().succ_opt().unwrap(), amount: 50., reason: "registered dividend".into() });
account.prune_flat_positions();
assert_eq!(account.market_value(), 0.);
assert_eq!(account.cash(), 1000.);
assert_eq!(account.total_equity(), 1050.);
assert!(account.deposit_withdraw(-1001.).is_err());
settle_receivables(date().succ_opt().unwrap(), &data(false), &mut account, &mut Vec::new(), false, None).unwrap();
assert_eq!(account.cash(), 1050.);
assert_eq!(account.total_equity(), 1050.);
}
#[test]
+12 -1
View File
@@ -1485,6 +1485,16 @@ impl PortfolioState {
&self.cash_receivables
}
pub fn cash_receivable_value(&self) -> f64 {
self.cash_receivables.iter().try_fold(FixedMoney::ZERO, |total, receivable| {
let amount = fixed_money(receivable.amount, "cash receivable asset")?;
if amount < FixedMoney::ZERO {
return Err("cash receivable asset cannot be negative".to_string());
}
total.checked_add(amount).ok_or_else(|| "cash receivable asset overflow".to_string())
}).expect("cash receivable asset must be valid fixed-point money").to_f64()
}
pub fn begin_trading_day(&mut self) {
self.day_sold_symbols.clear();
for position in self.positions.values_mut() {
@@ -1589,7 +1599,8 @@ impl PortfolioState {
}
pub fn total_equity(&self) -> f64 {
self.cash.to_f64() + self.market_value() - self.cash_liabilities.to_f64()
self.cash.to_f64() + self.market_value() + self.cash_receivable_value()
- self.cash_liabilities.to_f64()
}
pub fn total_value(&self) -> f64 {