统一策略成交保护与锁定周期并修正日期条件覆盖

This commit is contained in:
boris
2026-09-11 09:52:04 +08:00
parent 6684f48f95
commit 33924b1fba
9 changed files with 1008 additions and 11 deletions
+25
View File
@@ -60,6 +60,8 @@ pub struct PositionLot {
pub struct Position {
pub symbol: String,
pub quantity: u32,
opened_date: Option<NaiveDate>,
last_buy_date: Option<NaiveDate>,
// ALV-compatible moving average execution price; partial sells do not rebase it.
pub average_price: f64,
// ALV-compatible moving average including buy costs; partial sells do not rebase it.
@@ -88,6 +90,8 @@ impl Position {
Self {
symbol: symbol.into(),
quantity: 0,
opened_date: None,
last_buy_date: None,
average_price: 0.0,
average_cost: 0.0,
last_price: 0.0,
@@ -114,6 +118,12 @@ impl Position {
self.quantity == 0
}
pub fn opened_date(&self) -> Option<NaiveDate> {
self.opened_date
}
pub fn last_buy_date(&self) -> Option<NaiveDate> { self.last_buy_date }
pub fn buy(&mut self, date: NaiveDate, quantity: u32, price: f64) {
self.buy_with_mark_price(date, quantity, price, price);
}
@@ -130,6 +140,10 @@ impl Position {
}
let previous_quantity = self.quantity;
self.last_buy_date = Some(self.last_buy_date.map_or(date, |previous| previous.max(date)));
if previous_quantity == 0 {
self.opened_date = Some(date);
}
let previous_average_price = self.average_price;
let previous_average_cost = self.average_cost;
let gross_amount = fixed_money_or_panic(
@@ -267,6 +281,7 @@ impl Position {
.checked_add(total_proceeds)
.ok_or_else(|| "fixed-point day sell value overflow".to_string())?;
if self.quantity == 0 {
self.opened_date = None;
self.average_price = 0.0;
self.recalculate_average_cost();
} else {
@@ -1224,6 +1239,8 @@ impl PortfolioState {
}
let old_quantity = old_position.quantity;
let old_opened_date = old_position.opened_date;
let old_last_buy_date = old_position.last_buy_date;
let last_price = old_position.last_price;
let old_average_price = old_position.average_price;
let old_average_cost = old_position.average_cost;
@@ -1263,6 +1280,14 @@ impl PortfolioState {
.entry(new_symbol.to_string())
.or_insert_with(|| Position::new(new_symbol));
let successor_quantity_before = successor.quantity;
successor.opened_date = match (successor.opened_date, old_opened_date) {
(Some(current), Some(previous)) => Some(current.min(previous)),
(current, previous) => current.or(previous),
};
successor.last_buy_date = match (successor.last_buy_date, old_last_buy_date) {
(Some(current), Some(previous)) => Some(current.max(previous)),
(current, previous) => current.or(previous),
};
let successor_average_price_before = successor.average_price;
let successor_average_cost_before = successor.average_cost;
successor.lots.extend(converted_lots);