修复迟到成交跨公司行为的经济账本校正
This commit is contained in:
@@ -734,6 +734,10 @@ impl PortfolioState {
|
||||
pub fn new(initial_cash: f64) -> Self {
|
||||
let initial_cash = fixed_money(initial_cash, "initial cash")
|
||||
.expect("initial cash must be finite fixed-point money");
|
||||
Self::from_fixed_initial_cash(initial_cash)
|
||||
}
|
||||
|
||||
pub(crate) fn from_fixed_initial_cash(initial_cash: FixedMoney) -> Self {
|
||||
Self {
|
||||
initial_cash,
|
||||
units: initial_cash,
|
||||
@@ -756,6 +760,8 @@ impl PortfolioState {
|
||||
self.initial_cash.to_f64()
|
||||
}
|
||||
|
||||
pub(crate) fn initial_cash_fixed(&self) -> FixedMoney { self.initial_cash }
|
||||
|
||||
pub(crate) fn stock_pool_execution_state(&self,pool_id:&str)->crate::stock_pool_state::StockPoolExecutionState{
|
||||
self.stock_pool_states.get(pool_id).cloned().unwrap_or_default()
|
||||
}
|
||||
@@ -787,6 +793,46 @@ impl PortfolioState {
|
||||
self.cash.to_f64()
|
||||
}
|
||||
|
||||
pub(crate) fn cash_fixed(&self) -> FixedMoney { self.cash }
|
||||
|
||||
pub(crate) fn financial_replay_identity(&self) -> serde_json::Value {
|
||||
let positions = self.positions.iter().filter(|(_, position)| position.quantity > 0)
|
||||
.map(|(symbol, position)| {
|
||||
let mut lots = position.lots.iter().map(|lot| (lot.acquired_date, lot.quantity,
|
||||
lot.entry_value.to_decimal_string(), lot.cost_basis.to_decimal_string())).collect::<Vec<_>>();
|
||||
lots.sort();
|
||||
(symbol.clone(), serde_json::json!({"quantity":position.quantity,"lots":lots,
|
||||
"openedDate":position.opened_date,"lastBuyDate":position.last_buy_date}))
|
||||
}).collect::<std::collections::BTreeMap<_, _>>();
|
||||
let mut receivables = self.cash_receivables.iter().map(|row| (row.symbol.clone(), row.ex_date,
|
||||
row.payable_date, fixed_money_or_panic(row.amount, "receivable identity").to_decimal_string(), row.reason.clone())).collect::<Vec<_>>();
|
||||
receivables.sort();
|
||||
serde_json::json!({"cash":self.cash.to_decimal_string(),"positions":positions,"receivables":receivables})
|
||||
}
|
||||
|
||||
pub(crate) fn financial_position_basis(&self, symbol: &str) -> FixedMoney {
|
||||
self.positions.get(symbol).map_or(FixedMoney::ZERO, Position::total_cost_basis)
|
||||
}
|
||||
|
||||
pub(crate) fn replace_replayed_financial_book(&mut self, mut replayed: PortfolioState) -> Result<(), String> {
|
||||
if replayed.cash < FixedMoney::ZERO || replayed.initial_cash != self.initial_cash {
|
||||
return Err("manual corporate replay changed initial capital or borrowed cash".into());
|
||||
}
|
||||
for (symbol, position) in &mut replayed.positions {
|
||||
if let Some(current) = self.positions.get(symbol).filter(|current| current.quantity > 0) {
|
||||
position.last_price = current.last_price;
|
||||
position.refresh_day_pnl();
|
||||
}
|
||||
}
|
||||
self.cash = replayed.cash;
|
||||
self.positions = replayed.positions;
|
||||
self.cash_receivables = replayed.cash_receivables;
|
||||
self.day_sold_symbols = replayed.day_sold_symbols;
|
||||
// Existing issued units, explicit cash-flow/financing facts, and task
|
||||
// target state are observed controls, not counterfactual new orders.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn external_cash_flow_total(&self) -> f64 {
|
||||
self.external_cash_flow_total.to_f64()
|
||||
}
|
||||
@@ -822,9 +868,13 @@ impl PortfolioState {
|
||||
}
|
||||
|
||||
pub fn apply_cash_delta(&mut self, delta: f64) -> Result<(), String> {
|
||||
self.apply_cash_delta_fixed(fixed_money(delta, "cash delta")?)
|
||||
}
|
||||
|
||||
pub(crate) fn apply_cash_delta_fixed(&mut self, delta: FixedMoney) -> Result<(), String> {
|
||||
self.cash = self
|
||||
.cash
|
||||
.checked_add(fixed_money(delta, "cash delta")?)
|
||||
.checked_add(delta)
|
||||
.ok_or_else(|| "fixed-point cash overflow".to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user