按顺序结算多笔现金应收
This commit is contained in:
@@ -3242,14 +3242,16 @@ where
|
|||||||
notes: &mut Vec<String>,
|
notes: &mut Vec<String>,
|
||||||
) -> Result<BrokerExecutionReport, BacktestError> {
|
) -> Result<BrokerExecutionReport, BacktestError> {
|
||||||
let mut report = BrokerExecutionReport::default();
|
let mut report = BrokerExecutionReport::default();
|
||||||
let settled = portfolio.settle_cash_receivables(date);
|
let due = portfolio.take_due_cash_receivables(date);
|
||||||
for receivable in settled {
|
for receivable in due {
|
||||||
|
let cash_before = portfolio.cash();
|
||||||
|
portfolio
|
||||||
|
.settle_cash_receivable(&receivable)
|
||||||
|
.map_err(BacktestError::Execution)?;
|
||||||
let mut note = format!(
|
let mut note = format!(
|
||||||
"cash_receivable_settled {} ex_date={} payable_date={} cash={:.2}",
|
"cash_receivable_settled {} ex_date={} payable_date={} cash={:.2}",
|
||||||
receivable.symbol, receivable.ex_date, receivable.payable_date, receivable.amount
|
receivable.symbol, receivable.ex_date, receivable.payable_date, receivable.amount
|
||||||
);
|
);
|
||||||
let cash_before = portfolio.cash() - receivable.amount;
|
|
||||||
|
|
||||||
if self.dividend_reinvestment
|
if self.dividend_reinvestment
|
||||||
&& receivable.reason.starts_with("cash_dividend")
|
&& receivable.reason.starts_with("cash_dividend")
|
||||||
&& receivable.amount > 0.0
|
&& receivable.amount > 0.0
|
||||||
|
|||||||
@@ -778,25 +778,23 @@ impl PortfolioState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn settle_cash_receivables(&mut self, date: NaiveDate) -> Vec<CashReceivable> {
|
pub fn take_due_cash_receivables(&mut self, date: NaiveDate) -> Vec<CashReceivable> {
|
||||||
let mut settled = Vec::new();
|
let mut due = Vec::new();
|
||||||
let mut pending = Vec::new();
|
let mut pending = Vec::new();
|
||||||
for receivable in self.cash_receivables.drain(..) {
|
for receivable in self.cash_receivables.drain(..) {
|
||||||
if receivable.payable_date <= date {
|
if receivable.payable_date <= date {
|
||||||
let amount = Self::fixed_money(receivable.amount, "cash receivable")
|
due.push(receivable);
|
||||||
.expect("cash receivable must be finite fixed-point money");
|
|
||||||
self.cash = self
|
|
||||||
.cash
|
|
||||||
.checked_add(amount)
|
|
||||||
.expect("fixed-point cash overflow while settling receivable");
|
|
||||||
settled.push(receivable);
|
|
||||||
} else {
|
} else {
|
||||||
pending.push(receivable);
|
pending.push(receivable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.cash_receivables = pending;
|
self.cash_receivables = pending;
|
||||||
self.refresh_dividend_receivables();
|
self.refresh_dividend_receivables();
|
||||||
settled
|
due
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn settle_cash_receivable(&mut self, receivable: &CashReceivable) -> Result<(), String> {
|
||||||
|
self.apply_cash_delta(receivable.amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cash_receivables(&self) -> &[CashReceivable] {
|
pub fn cash_receivables(&self) -> &[CashReceivable] {
|
||||||
|
|||||||
@@ -49,14 +49,30 @@ fn portfolio_settles_cash_receivable_on_payable_date() {
|
|||||||
amount: 500.0,
|
amount: 500.0,
|
||||||
reason: "cash_dividend 0.5".to_string(),
|
reason: "cash_dividend 0.5".to_string(),
|
||||||
});
|
});
|
||||||
|
portfolio.add_cash_receivable(CashReceivable {
|
||||||
|
symbol: "000002.SZ".to_string(),
|
||||||
|
ex_date: d(2025, 1, 2),
|
||||||
|
payable_date: d(2025, 1, 5),
|
||||||
|
amount: 250.0,
|
||||||
|
reason: "cash_dividend 0.25".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
let settled_early = portfolio.settle_cash_receivables(d(2025, 1, 4));
|
let due_early = portfolio.take_due_cash_receivables(d(2025, 1, 4));
|
||||||
assert!(settled_early.is_empty());
|
assert!(due_early.is_empty());
|
||||||
assert!((portfolio.cash() - 1_000_000.0).abs() < 1e-9);
|
assert!((portfolio.cash() - 1_000_000.0).abs() < 1e-9);
|
||||||
|
|
||||||
let settled = portfolio.settle_cash_receivables(d(2025, 1, 5));
|
let due = portfolio.take_due_cash_receivables(d(2025, 1, 5));
|
||||||
assert_eq!(settled.len(), 1);
|
assert_eq!(due.len(), 2);
|
||||||
assert!((portfolio.cash() - 1_000_500.0).abs() < 1e-9);
|
let mut cash_chain = Vec::new();
|
||||||
|
for receivable in &due {
|
||||||
|
let cash_before = portfolio.cash();
|
||||||
|
portfolio.settle_cash_receivable(receivable).unwrap();
|
||||||
|
cash_chain.push((cash_before, portfolio.cash()));
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
cash_chain,
|
||||||
|
vec![(1_000_000.0, 1_000_500.0), (1_000_500.0, 1_000_750.0)]
|
||||||
|
);
|
||||||
assert!(portfolio.cash_receivables().is_empty());
|
assert!(portfolio.cash_receivables().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user