修复 AiQuant 微盘回测撮合语义

This commit is contained in:
boris
2026-05-13 18:43:02 +08:00
parent 2165831708
commit db72f6f515
8 changed files with 849 additions and 162 deletions

View File

@@ -551,6 +551,15 @@ impl PortfolioState {
field: PriceField,
) -> Result<(), DataSetError> {
for position in self.positions.values_mut() {
if field == PriceField::Close
&& position.day_buy_quantity > 0
&& position.sellable_qty(date) == 0
&& position.last_price.is_finite()
&& position.last_price > 0.0
{
position.refresh_day_pnl();
continue;
}
let price = data
.price(date, &position.symbol, field)
.or_else(|| data.price_on_or_before(date, &position.symbol, field))
@@ -1066,6 +1075,111 @@ mod tests {
assert!(position.position_pnl.abs() < 1e-6);
}
#[test]
fn portfolio_marks_same_day_buy_at_fill_until_next_trading_day() {
let buy_date = NaiveDate::from_ymd_opt(2025, 2, 10).unwrap();
let next_date = NaiveDate::from_ymd_opt(2025, 2, 11).unwrap();
let symbol = "002652.SZ";
let mut portfolio = PortfolioState::new(20_000.0);
portfolio.position_mut(symbol).buy(buy_date, 1300, 3.01);
let dataset = DataSet::from_components(
vec![Instrument {
symbol: symbol.to_string(),
name: "Same Day Buy Test".to_string(),
board: "SZ".to_string(),
round_lot: 100,
listed_at: None,
delisted_at: None,
status: "active".to_string(),
}],
vec![
DailyMarketSnapshot {
date: buy_date,
symbol: symbol.to_string(),
timestamp: None,
day_open: 2.99,
open: 2.99,
high: 3.06,
low: 2.98,
close: 3.06,
last_price: 3.06,
bid1: 3.01,
ask1: 3.02,
prev_close: 2.98,
volume: 152_975,
tick_volume: 152_975,
bid1_volume: 338,
ask1_volume: 2476,
trading_phase: None,
paused: false,
upper_limit: 3.28,
lower_limit: 2.68,
price_tick: 0.01,
},
DailyMarketSnapshot {
date: next_date,
symbol: symbol.to_string(),
timestamp: None,
day_open: 3.03,
open: 3.03,
high: 3.08,
low: 3.00,
close: 3.07,
last_price: 3.07,
bid1: 3.06,
ask1: 3.07,
prev_close: 3.06,
volume: 160_000,
tick_volume: 160_000,
bid1_volume: 1000,
ask1_volume: 1000,
trading_phase: None,
paused: false,
upper_limit: 3.37,
lower_limit: 2.75,
price_tick: 0.01,
},
],
Vec::new(),
Vec::new(),
vec![
BenchmarkSnapshot {
date: buy_date,
benchmark: "000852.SH".to_string(),
open: 1000.0,
close: 1000.0,
prev_close: 999.0,
volume: 1000,
},
BenchmarkSnapshot {
date: next_date,
benchmark: "000852.SH".to_string(),
open: 1001.0,
close: 1001.0,
prev_close: 1000.0,
volume: 1000,
},
],
)
.expect("dataset");
portfolio
.update_prices(buy_date, &dataset, PriceField::Close)
.expect("same day close");
let position = portfolio.position(symbol).expect("position");
assert!((position.last_price - 3.01).abs() < 1e-9);
assert!((position.market_value() - 3913.0).abs() < 1e-6);
portfolio.begin_trading_day();
portfolio
.update_prices(next_date, &dataset, PriceField::Close)
.expect("next day close");
let position = portfolio.position(symbol).expect("position");
assert!((position.last_price - 3.07).abs() < 1e-9);
assert!((position.market_value() - 3991.0).abs() < 1e-6);
}
#[test]
fn position_tracks_day_lifecycle_fields() {
let prev_date = NaiveDate::from_ymd_opt(2025, 1, 2).unwrap();