修正回测部分成交终态合同

This commit is contained in:
boris
2026-08-26 18:28:19 +08:00
committed by Boris
parent 8303a6477b
commit bc0f5f6089
3 changed files with 103 additions and 37 deletions
+13 -36
View File
@@ -29,6 +29,15 @@ pub struct BrokerExecutionReport {
pub diagnostics: Vec<String>,
}
impl BrokerExecutionReport {
fn validate(&self) -> Result<(), BacktestError> {
for event in &self.order_events {
event.validate().map_err(BacktestError::Execution)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
struct ExecutionLeg {
price: f64,
@@ -1038,6 +1047,7 @@ where
self.runtime_target_position_limit
.set(previous_target_position_limit);
portfolio.prune_flat_positions();
report.validate()?;
return Ok(report);
}
@@ -1153,6 +1163,7 @@ where
}
portfolio.prune_flat_positions();
report.validate()?;
Ok(report)
}
@@ -3850,7 +3861,7 @@ where
let status = if keep_open {
OrderStatus::PartiallyFilled
} else if filled_qty < requested_qty {
final_partial_fill_status(partial_fill_reason.as_deref())
OrderStatus::Canceled
} else {
OrderStatus::Filled
};
@@ -3862,14 +3873,6 @@ where
"order_partial_fill symbol={symbol} side=sell requested={requested_qty} filled={filled_qty} reason={detail}; remaining open"
));
format!("{reason}: partial fill due to {detail}; remaining quantity pending")
} else if status == OrderStatus::PartiallyFilled {
let detail = partial_fill_reason
.as_deref()
.unwrap_or("remaining quantity could not be filled");
report.diagnostics.push(format!(
"order_partial_fill symbol={symbol} side=sell requested={requested_qty} filled={filled_qty} reason={detail}"
));
format!("{reason}: partial fill due to {detail}")
} else if status == OrderStatus::Canceled && filled_qty < requested_qty {
let detail = partial_fill_reason
.as_deref()
@@ -5506,7 +5509,7 @@ where
let status = if keep_open {
OrderStatus::PartiallyFilled
} else if filled_qty < requested_qty {
final_partial_fill_status(partial_fill_reason.as_deref())
OrderStatus::Canceled
} else {
OrderStatus::Filled
};
@@ -5518,14 +5521,6 @@ where
"order_partial_fill symbol={symbol} side=buy requested={requested_qty} filled={filled_qty} reason={detail}; remaining open"
));
format!("{reason}: partial fill due to {detail}; remaining quantity pending")
} else if status == OrderStatus::PartiallyFilled {
let detail = partial_fill_reason
.as_deref()
.unwrap_or("remaining quantity could not be filled");
report.diagnostics.push(format!(
"order_partial_fill symbol={symbol} side=buy requested={requested_qty} filled={filled_qty} reason={detail}"
));
format!("{reason}: partial fill due to {detail}")
} else if status == OrderStatus::Canceled && filled_qty < requested_qty {
let detail = partial_fill_reason
.as_deref()
@@ -6589,24 +6584,6 @@ fn zero_fill_status_for_reason(reason: &str) -> OrderStatus {
}
}
fn final_partial_fill_status(partial_reason: Option<&str>) -> OrderStatus {
match partial_reason {
Some(reason)
if reason.contains("market liquidity or volume limit")
|| reason.contains("intraday quote liquidity exhausted")
|| reason.contains("no execution quotes at or before start")
|| reason.contains("no execution quotes after start")
|| reason.contains("upper_limit")
|| reason.contains("lower_limit")
|| reason.contains("open at or above upper limit")
|| reason.contains("open at or below lower limit") =>
{
OrderStatus::Canceled
}
_ => OrderStatus::PartiallyFilled,
}
}
fn price_field_name(field: PriceField) -> &'static str {
match field {
PriceField::DayOpen => "day_open",