Add futures depth matching and mod loader
This commit is contained in:
@@ -7,7 +7,7 @@ use thiserror::Error;
|
||||
use crate::broker::{BrokerExecutionReport, BrokerSimulator, MatchingType};
|
||||
use crate::cost::CostModel;
|
||||
use crate::data::{BenchmarkSnapshot, DataSet, DataSetError, PriceField};
|
||||
use crate::event_bus::{BacktestProcessMod, ProcessEventBus};
|
||||
use crate::event_bus::{BacktestProcessMod, BacktestProcessModLoader, ProcessEventBus};
|
||||
use crate::events::{
|
||||
AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent,
|
||||
ProcessEventKind,
|
||||
@@ -401,6 +401,22 @@ impl<S, C, R> BacktestEngine<S, C, R> {
|
||||
{
|
||||
self.process_event_bus.install_mod(module);
|
||||
}
|
||||
|
||||
pub fn install_process_mod_loader(
|
||||
&mut self,
|
||||
loader: &mut BacktestProcessModLoader,
|
||||
) -> Vec<String> {
|
||||
self.process_event_bus.install_mod_loader(loader)
|
||||
}
|
||||
|
||||
pub fn install_enabled_process_mods(
|
||||
&mut self,
|
||||
loader: &mut BacktestProcessModLoader,
|
||||
enabled_names: &[String],
|
||||
) -> Vec<String> {
|
||||
self.process_event_bus
|
||||
.install_enabled_mods(loader, enabled_names)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, C, R> BacktestEngine<S, C, R>
|
||||
@@ -1119,6 +1135,15 @@ where
|
||||
intent: &FuturesOrderIntent,
|
||||
) -> Option<(f64, u32)> {
|
||||
let snapshot = self.data.market(date, &intent.symbol);
|
||||
if matches!(
|
||||
self.broker.matching_type(),
|
||||
MatchingType::NextTickBestCounterparty | MatchingType::CounterpartyOffer
|
||||
) {
|
||||
let depth = self.data.order_book_depth_on(date, &intent.symbol);
|
||||
if !depth.is_empty() {
|
||||
return self.resolve_futures_depth_fill(date, intent, snapshot);
|
||||
}
|
||||
}
|
||||
let quotes = self.data.execution_quotes_on(date, &intent.symbol);
|
||||
for quote in quotes {
|
||||
let price = match self.broker.matching_type() {
|
||||
@@ -1172,6 +1197,63 @@ where
|
||||
None
|
||||
}
|
||||
|
||||
fn resolve_futures_depth_fill(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
intent: &FuturesOrderIntent,
|
||||
snapshot: Option<&crate::data::DailyMarketSnapshot>,
|
||||
) -> Option<(f64, u32)> {
|
||||
let depth = self.data.order_book_depth_on(date, &intent.symbol);
|
||||
let mut cursor = 0usize;
|
||||
while cursor < depth.len() {
|
||||
let timestamp = depth[cursor].timestamp;
|
||||
let start = cursor;
|
||||
while cursor < depth.len() && depth[cursor].timestamp == timestamp {
|
||||
cursor += 1;
|
||||
}
|
||||
let mut levels = depth[start..cursor].iter().collect::<Vec<_>>();
|
||||
levels.sort_by(|left, right| left.level.cmp(&right.level));
|
||||
|
||||
let mut filled_quantity = 0_u32;
|
||||
let mut gross_amount = 0.0_f64;
|
||||
for level in levels {
|
||||
let Some(price) = level.executable_price(intent.side()) else {
|
||||
continue;
|
||||
};
|
||||
let can_trade = if let Some(snapshot) = snapshot {
|
||||
self.futures_price_can_trade(snapshot, intent.side(), price, intent.limit_price)
|
||||
} else {
|
||||
futures_limit_satisfied(intent.side(), price, intent.limit_price)
|
||||
};
|
||||
if !can_trade {
|
||||
if intent.limit_price.is_some() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let available_quantity =
|
||||
level.executable_volume(intent.side()).min(u32::MAX as u64) as u32;
|
||||
if available_quantity == 0 {
|
||||
continue;
|
||||
}
|
||||
let remaining = intent.quantity.saturating_sub(filled_quantity);
|
||||
if remaining == 0 {
|
||||
break;
|
||||
}
|
||||
let take_quantity = remaining.min(available_quantity);
|
||||
gross_amount += price * take_quantity as f64;
|
||||
filled_quantity += take_quantity;
|
||||
if filled_quantity >= intent.quantity {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if filled_quantity > 0 {
|
||||
return Some((gross_amount / filled_quantity as f64, filled_quantity));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn futures_price_can_trade(
|
||||
&self,
|
||||
snapshot: &crate::data::DailyMarketSnapshot,
|
||||
|
||||
Reference in New Issue
Block a user