Add order runtime lookup

This commit is contained in:
boris
2026-04-23 19:57:02 -07:00
parent c12a883d28
commit 9f10afddec
8 changed files with 351 additions and 8 deletions

View File

@@ -9,7 +9,7 @@ use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime};
use crate::cost::ChinaAShareCostModel;
use crate::data::{DailyMarketSnapshot, DataSet, IntradayExecutionQuote, PriceBar, PriceField};
use crate::engine::BacktestError;
use crate::events::{OrderSide, OrderStatus, ProcessEvent};
use crate::events::{FillEvent, OrderEvent, OrderSide, OrderStatus, ProcessEvent};
use crate::instrument::Instrument;
use crate::portfolio::PortfolioState;
use crate::scheduler::ScheduleRule;
@@ -80,6 +80,21 @@ pub struct OpenOrderView {
pub reason: String,
}
#[derive(Debug, Clone)]
pub struct OrderRuntimeView {
pub order_id: u64,
pub symbol: String,
pub side: OrderSide,
pub requested_quantity: u32,
pub filled_quantity: u32,
pub unfilled_quantity: u32,
pub status: OrderStatus,
pub avg_price: f64,
pub transaction_cost: f64,
pub limit_price: f64,
pub reason: String,
}
pub struct StrategyContext<'a> {
pub execution_date: NaiveDate,
pub decision_date: NaiveDate,
@@ -92,6 +107,8 @@ pub struct StrategyContext<'a> {
pub process_events: &'a [ProcessEvent],
pub active_process_event: Option<&'a ProcessEvent>,
pub active_datetime: Option<NaiveDateTime>,
pub order_events: &'a [OrderEvent],
pub fills: &'a [FillEvent],
}
impl StrategyContext<'_> {
@@ -215,6 +232,97 @@ impl StrategyContext<'_> {
.unwrap_or(0)
}
pub fn order(&self, order_id: u64) -> Option<OrderRuntimeView> {
let fills = self
.fills
.iter()
.filter(|fill| fill.order_id == Some(order_id))
.collect::<Vec<_>>();
let filled_quantity = fills.iter().map(|fill| fill.quantity).sum::<u32>();
let gross_amount = fills.iter().map(|fill| fill.gross_amount).sum::<f64>();
let transaction_cost = fills
.iter()
.map(|fill| fill.commission + fill.stamp_tax)
.sum::<f64>();
let avg_price = if filled_quantity == 0 {
0.0
} else {
gross_amount / filled_quantity as f64
};
if let Some(order) = self
.open_orders
.iter()
.find(|order| order.order_id == order_id)
{
let filled_quantity = order.filled_quantity.max(filled_quantity);
return Some(OrderRuntimeView {
order_id,
symbol: order.symbol.clone(),
side: order.side,
requested_quantity: order.requested_quantity,
filled_quantity,
unfilled_quantity: order
.unfilled_quantity
.min(order.requested_quantity.saturating_sub(filled_quantity)),
status: order.status,
avg_price: if avg_price > 0.0 {
avg_price
} else {
order.avg_price
},
transaction_cost: if transaction_cost > 0.0 {
transaction_cost
} else {
order.transaction_cost
},
limit_price: order.limit_price,
reason: order.reason.clone(),
});
}
let latest_event = self
.order_events
.iter()
.rev()
.filter(|event| event.order_id == Some(order_id))
.next()?;
let filled_quantity = latest_event.filled_quantity.max(filled_quantity);
Some(OrderRuntimeView {
order_id,
symbol: latest_event.symbol.clone(),
side: latest_event.side,
requested_quantity: latest_event.requested_quantity,
filled_quantity,
unfilled_quantity: latest_event
.requested_quantity
.saturating_sub(filled_quantity),
status: latest_event.status,
avg_price,
transaction_cost,
limit_price: 0.0,
reason: latest_event.reason.clone(),
})
}
pub fn order_status(&self, order_id: u64) -> &'static str {
self.order(order_id)
.map(|order| order.status.as_str())
.unwrap_or("")
}
pub fn order_avg_price(&self, order_id: u64) -> f64 {
self.order(order_id)
.map(|order| order.avg_price)
.unwrap_or(0.0)
}
pub fn order_transaction_cost(&self, order_id: u64) -> f64 {
self.order(order_id)
.map(|order| order.transaction_cost)
.unwrap_or(0.0)
}
pub fn available_sellable_qty(&self, symbol: &str, raw_sellable_qty: u32) -> u32 {
raw_sellable_qty.saturating_sub(self.symbol_open_sell_quantity(symbol))
}