修正回测部分成交终态合同
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user