修正下一开盘目标仓位计算

This commit is contained in:
boris
2026-07-12 14:59:12 +08:00
parent 0ea5fae69d
commit bacb70e327
6 changed files with 444 additions and 77 deletions
+103 -12
View File
@@ -1728,6 +1728,7 @@ where
daily_holdings: Vec::new(),
metrics: BacktestMetrics::default(),
};
let mut stock_equity_by_date = BTreeMap::<NaiveDate, f64>::new();
for (execution_idx, execution_date) in execution_dates.iter().copied().enumerate() {
let mut corporate_action_notes = Vec::new();
@@ -1875,8 +1876,12 @@ where
process_events: day_process_events,
});
result.process_events.append(&mut process_events);
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
continue;
};
let decision_total_equity = (decision_date < execution_date)
.then(|| stock_equity_by_date.get(&decision_date).copied())
.flatten();
let mut process_events = Vec::new();
let mut directive_report = BrokerExecutionReport::default();
let pre_open_orders = self.open_order_views();
@@ -2073,10 +2078,11 @@ where
None,
None,
)?;
let mut report = self.broker.execute_with_event_dates(
let mut report = self.broker.execute_with_event_dates_and_decision_equity(
execution_date,
decision_date,
decision_date,
decision_total_equity,
&mut portfolio,
&self.data,
&auction_decision,
@@ -2321,10 +2327,11 @@ where
None,
None,
)?;
let mut intraday_report = self.broker.execute_with_event_dates(
let mut intraday_report = self.broker.execute_with_event_dates_and_decision_equity(
execution_date,
decision_date,
decision_date,
decision_total_equity,
&mut portfolio,
&self.data,
&decision,
@@ -2492,16 +2499,19 @@ where
Some(minute_time),
Some(minute_time),
)?;
let mut minute_report = self.broker.execute_between_with_event_dates(
execution_date,
decision_date,
decision_date,
&mut portfolio,
&self.data,
&minute_decision,
Some(minute_time),
Some(minute_time),
)?;
let mut minute_report = self
.broker
.execute_between_with_event_dates_and_decision_equity(
execution_date,
decision_date,
decision_date,
decision_total_equity,
&mut portfolio,
&self.data,
&minute_decision,
Some(minute_time),
Some(minute_time),
)?;
let post_minute_open_orders = self.open_order_views();
publish_process_events(
&mut self.strategy,
@@ -2888,6 +2898,7 @@ where
process_events: day_process_events,
});
result.process_events.extend(process_events);
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
}
if let Some(last_date) = execution_dates.last().copied() {
@@ -4323,6 +4334,43 @@ mod tests {
}
}
#[derive(Debug)]
struct ScheduledTargetPercentStrategy {
first_decision_date: NaiveDate,
second_decision_date: NaiveDate,
}
impl Strategy for ScheduledTargetPercentStrategy {
fn name(&self) -> &str {
"scheduled_target_percent"
}
fn on_day(
&mut self,
ctx: &StrategyContext<'_>,
) -> Result<StrategyDecision, super::BacktestError> {
let order_intents = if ctx.decision_date == self.first_decision_date {
vec![OrderIntent::Shares {
symbol: SYMBOL.to_string(),
quantity: 1_000,
reason: "initial_position".to_string(),
}]
} else if ctx.decision_date == self.second_decision_date {
vec![OrderIntent::TargetPercent {
symbol: SYMBOL.to_string(),
target_percent: 0.5,
reason: "frozen_target_percent".to_string(),
}]
} else {
Vec::new()
};
Ok(StrategyDecision {
order_intents,
..StrategyDecision::default()
})
}
}
#[derive(Debug)]
struct ScheduledEligibleUniverseBuyStrategy {
rule: ScheduleRule,
@@ -5003,6 +5051,49 @@ mod tests {
assert_eq!(result.fills[0].quantity, 8_300);
}
#[test]
fn next_bar_open_target_percent_freezes_decision_day_equity() {
let first = d(2025, 1, 2);
let second = d(2025, 1, 3);
let third = d(2025, 1, 6);
let dataset = dataset_from_market_and_candidates(
vec![
market(first, 10.0, 10.0),
market(second, 10.0, 10.0),
market(third, 20.0, 20.0),
],
vec![candidate(first), candidate(second), candidate(third)],
);
let config = BacktestConfig {
initial_cash: 100_000.0,
benchmark_code: "000852.SH".to_string(),
start_date: Some(first),
end_date: Some(third),
decision_lag_trading_days: 1,
execution_price_field: PriceField::Open,
};
let result = BacktestEngine::new(
dataset,
ScheduledTargetPercentStrategy {
first_decision_date: first,
second_decision_date: second,
},
scheduled_next_open_broker(FidcRiskControlConfig::default()),
config,
)
.run()
.expect("backtest run");
assert_eq!(result.fills.len(), 2, "fills={:?}", result.fills);
assert_eq!(result.fills[0].date, second);
assert_eq!(result.fills[0].quantity, 1_000);
assert_eq!(result.fills[1].date, third);
assert_eq!(result.fills[1].price, 20.0);
assert_eq!(result.fills[1].quantity, 1_400);
assert_eq!(result.fills[1].decision_date, Some(second));
}
#[test]
fn next_bar_open_executes_last_decision_without_execution_day_factor_snapshot() {
let first = d(2025, 1, 2);