Add futures expiration schedule

This commit is contained in:
boris
2026-04-23 20:43:25 -07:00
parent 4755a59a5b
commit 5653278576
3 changed files with 165 additions and 3 deletions

View File

@@ -1,4 +1,4 @@
use std::collections::BTreeSet;
use std::collections::{BTreeMap, BTreeSet};
use chrono::NaiveDate;
use serde::Serialize;
@@ -104,6 +104,7 @@ pub struct BacktestEngine<S, C, R> {
subscriptions: BTreeSet<String>,
futures_account: Option<FuturesAccountState>,
next_futures_order_id: u64,
futures_expirations: BTreeMap<NaiveDate, BTreeMap<String, f64>>,
}
impl<S, C, R> BacktestEngine<S, C, R> {
@@ -124,6 +125,7 @@ impl<S, C, R> BacktestEngine<S, C, R> {
subscriptions: BTreeSet::new(),
futures_account: None,
next_futures_order_id: 1,
futures_expirations: BTreeMap::new(),
}
}
@@ -149,6 +151,27 @@ impl<S, C, R> BacktestEngine<S, C, R> {
self.futures_account.as_mut()
}
pub fn with_futures_expiration(
mut self,
date: NaiveDate,
symbol: impl Into<String>,
settlement_price: f64,
) -> Self {
self.futures_expirations
.entry(date)
.or_default()
.insert(symbol.into(), settlement_price);
self
}
pub fn with_futures_expirations(
mut self,
expirations: BTreeMap<NaiveDate, BTreeMap<String, f64>>,
) -> Self {
self.futures_expirations = expirations;
self
}
pub fn process_event_bus_mut(&mut self) -> &mut ProcessEventBus {
&mut self.process_event_bus
}
@@ -1412,6 +1435,8 @@ where
&mut settlement_decision,
&mut directive_report,
)?;
let futures_expiration_report = self.settle_futures_expirations(execution_date);
merge_broker_report(&mut directive_report, futures_expiration_report);
let dynamic_universe_snapshot = self.dynamic_universe.clone();
let subscriptions_snapshot = self.subscriptions.clone();
let management_fee_report = self.apply_management_fee(
@@ -1841,6 +1866,26 @@ where
report
}
fn settle_futures_expirations(&mut self, date: NaiveDate) -> BrokerExecutionReport {
let mut report = BrokerExecutionReport::default();
let Some(expirations) = self.futures_expirations.remove(&date) else {
return report;
};
let Some(account) = self.futures_account.as_mut() else {
report.diagnostics.push(format!(
"futures_expiration_skipped date={date} reason=no_future_account count={}",
expirations.len()
));
return report;
};
for (symbol, settlement_price) in expirations {
let futures_report =
account.expire_contract(date, &symbol, settlement_price, "data_driven_expiration");
merge_futures_report(&mut report, futures_report);
}
report
}
fn apply_management_fee(
&mut self,
execution_date: NaiveDate,