让实际换股持仓继承原策略保护期限
This commit is contained in:
@@ -709,6 +709,9 @@ pub struct PortfolioState {
|
||||
cash_receivables: Vec<CashReceivable>,
|
||||
pending_cash_flows: Vec<PendingCashFlow>,
|
||||
day_sold_symbols: BTreeSet<String>,
|
||||
// Observed holding conversions, never a catalog alias or a new target.
|
||||
// Kept after a position becomes flat so an active date lock is not lost.
|
||||
corporate_predecessors: BTreeMap<String, BTreeSet<String>>,
|
||||
stock_pool_states: std::collections::BTreeMap<String,crate::stock_pool_state::StockPoolExecutionState>,
|
||||
}
|
||||
|
||||
@@ -750,6 +753,7 @@ impl PortfolioState {
|
||||
cash_receivables: Vec::new(),
|
||||
pending_cash_flows: Vec::new(),
|
||||
day_sold_symbols: BTreeSet::new(),
|
||||
corporate_predecessors: BTreeMap::new(),
|
||||
stock_pool_states: std::collections::BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -762,6 +766,14 @@ impl PortfolioState {
|
||||
|
||||
pub(crate) fn initial_cash_fixed(&self) -> FixedMoney { self.initial_cash }
|
||||
|
||||
pub(crate) fn corporate_predecessors(&self, symbol: &str) -> impl Iterator<Item = &str> {
|
||||
self.corporate_predecessors.get(symbol).into_iter().flatten().map(String::as_str)
|
||||
}
|
||||
|
||||
pub(crate) fn observed_successor_symbols(&self) -> impl Iterator<Item = &str> {
|
||||
self.corporate_predecessors.keys().map(String::as_str)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -807,7 +819,11 @@ impl PortfolioState {
|
||||
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})
|
||||
let mut identity = serde_json::json!({"cash":self.cash.to_decimal_string(),"positions":positions,"receivables":receivables});
|
||||
if !self.corporate_predecessors.is_empty() {
|
||||
identity["corporatePredecessors"] = serde_json::json!(self.corporate_predecessors);
|
||||
}
|
||||
identity
|
||||
}
|
||||
|
||||
pub(crate) fn financial_position_basis(&self, symbol: &str) -> FixedMoney {
|
||||
@@ -828,6 +844,9 @@ impl PortfolioState {
|
||||
self.positions = replayed.positions;
|
||||
self.cash_receivables = replayed.cash_receivables;
|
||||
self.day_sold_symbols = replayed.day_sold_symbols;
|
||||
// Corrected actual receipts can prove a position was fully sold
|
||||
// before conversion. Do not retain a now-disproved financial link.
|
||||
self.corporate_predecessors = replayed.corporate_predecessors;
|
||||
// Existing issued units, explicit cash-flow/financing facts, and task
|
||||
// target state are observed controls, not counterfactual new orders.
|
||||
Ok(())
|
||||
@@ -1557,7 +1576,7 @@ impl PortfolioState {
|
||||
}
|
||||
successor.refresh_day_pnl();
|
||||
|
||||
Some(SuccessorConversionOutcome {
|
||||
let outcome = SuccessorConversionOutcome {
|
||||
old_symbol: old_symbol_owned,
|
||||
new_symbol: new_symbol.to_string(),
|
||||
old_quantity,
|
||||
@@ -1569,7 +1588,14 @@ impl PortfolioState {
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
})
|
||||
};
|
||||
if converted_quantity > 0 {
|
||||
let mut predecessors = self.corporate_predecessors.get(old_symbol).cloned().unwrap_or_default();
|
||||
predecessors.insert(old_symbol.to_owned());
|
||||
predecessors.remove(new_symbol);
|
||||
self.corporate_predecessors.entry(new_symbol.to_owned()).or_default().extend(predecessors);
|
||||
}
|
||||
Some(outcome)
|
||||
}
|
||||
|
||||
fn sum_fixed_money(
|
||||
|
||||
Reference in New Issue
Block a user