修复 AiQuant 微盘回测撮合语义
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user