修正部分卖出持仓成本

This commit is contained in:
boris
2026-07-08 02:44:31 +08:00
parent ca0471799b
commit 4ce52a7af6
+28 -1
View File
@@ -117,6 +117,7 @@ impl Position {
let mut remaining = quantity; let mut remaining = quantity;
let mut realized = 0.0; let mut realized = 0.0;
let mut realized_entry = 0.0; let mut realized_entry = 0.0;
let average_cost_before_sell = self.average_cost;
while remaining > 0 { while remaining > 0 {
let Some(first_lot) = self.lots.first_mut() else { let Some(first_lot) = self.lots.first_mut() else {
@@ -141,7 +142,13 @@ impl Position {
self.day_trade_quantity_delta -= quantity as i32; self.day_trade_quantity_delta -= quantity as i32;
self.day_sell_quantity += quantity; self.day_sell_quantity += quantity;
self.day_sell_value += execution_price * quantity as f64; self.day_sell_value += execution_price * quantity as f64;
self.recalculate_average_cost(); if self.quantity == 0 {
self.recalculate_average_cost();
} else if average_cost_before_sell.is_finite() && average_cost_before_sell > 0.0 {
self.average_cost = average_cost_before_sell;
} else {
self.recalculate_average_cost();
}
self.refresh_day_pnl(); self.refresh_day_pnl();
Ok(realized) Ok(realized)
} }
@@ -955,6 +962,26 @@ mod tests {
assert!((position.holding_return(6.06).unwrap() - (6.06 / 5.66 - 1.0)).abs() < 1e-12); assert!((position.holding_return(6.06).unwrap() - (6.06 / 5.66 - 1.0)).abs() < 1e-12);
} }
#[test]
fn partial_sell_preserves_remaining_average_cost() {
let date = NaiveDate::from_ymd_opt(2025, 1, 2).unwrap();
let mut position = Position::new("603958.SH");
position.buy(date, 800, 18.0981);
position.record_buy_trade_cost(800, 5.0);
position.buy(date, 1700, 19.4694);
position.record_buy_trade_cost(1700, 8.27451625);
position.buy(date, 200, 18.4584);
position.record_buy_trade_cost(200, 5.0);
position.buy(date, 100, 17.8378);
position.record_buy_trade_cost(100, 5.0);
let average_cost_before = position.average_cost;
position.sell(2700, 16.8331).expect("partial sell");
assert_eq!(position.quantity, 100);
assert!((position.average_cost - average_cost_before).abs() < 1e-12);
}
#[test] #[test]
fn holdings_summary_reports_entry_price_pnl_excluding_buy_commission() { fn holdings_summary_reports_entry_price_pnl_excluding_buy_commission() {
let date = NaiveDate::from_ymd_opt(2025, 1, 2).unwrap(); let date = NaiveDate::from_ymd_opt(2025, 1, 2).unwrap();