Add futures expiration settlement
This commit is contained in:
@@ -679,6 +679,61 @@ impl FuturesAccountState {
|
||||
report
|
||||
}
|
||||
|
||||
pub fn expire_contract(
|
||||
&mut self,
|
||||
date: NaiveDate,
|
||||
symbol: &str,
|
||||
settlement_price: f64,
|
||||
reason: impl Into<String>,
|
||||
) -> FuturesExecutionReport {
|
||||
let reason = reason.into();
|
||||
let keys = self
|
||||
.positions
|
||||
.keys()
|
||||
.filter(|(position_symbol, _)| position_symbol == symbol)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let mut combined = FuturesExecutionReport::default();
|
||||
for (position_symbol, direction) in keys {
|
||||
let Some(position) = self.position(&position_symbol, direction) else {
|
||||
continue;
|
||||
};
|
||||
if position.quantity == 0 {
|
||||
continue;
|
||||
}
|
||||
let price = if settlement_price.is_finite() && settlement_price > 0.0 {
|
||||
settlement_price
|
||||
} else {
|
||||
position.last_price
|
||||
};
|
||||
let intent = FuturesOrderIntent::close(
|
||||
position_symbol.clone(),
|
||||
direction,
|
||||
FuturesPositionEffect::Close,
|
||||
FuturesContractSpec::new(
|
||||
position.contract_multiplier,
|
||||
position.margin_rate,
|
||||
position.margin_rate,
|
||||
),
|
||||
position.quantity,
|
||||
price,
|
||||
0.0,
|
||||
format!("{reason}: futures_expiration_settlement"),
|
||||
);
|
||||
let report = self.execute_order(date, None, intent);
|
||||
combined.order_events.extend(report.order_events);
|
||||
combined.fill_events.extend(report.fill_events);
|
||||
combined.position_events.extend(report.position_events);
|
||||
combined.account_events.extend(report.account_events);
|
||||
combined.diagnostics.extend(report.diagnostics);
|
||||
}
|
||||
combined.diagnostics.push(format!(
|
||||
"futures_expiration_settlement symbol={symbol} closed_orders={}",
|
||||
combined.order_events.len()
|
||||
));
|
||||
combined
|
||||
}
|
||||
|
||||
pub fn mark_price(&mut self, symbol: &str, direction: FuturesDirection, price: f64) {
|
||||
if let Some(position) = self.positions.get_mut(&(symbol.to_string(), direction)) {
|
||||
position.mark_price(price);
|
||||
|
||||
@@ -161,3 +161,30 @@ fn futures_open_order_rejects_when_margin_is_insufficient() {
|
||||
assert!(account.position("IF2501", FuturesDirection::Long).is_none());
|
||||
assert!((account.total_cash() - 10_000.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn futures_expiration_settlement_closes_all_contract_directions() {
|
||||
let spec = FuturesContractSpec::new(300.0, 0.12, 0.14);
|
||||
let mut account = FuturesAccountState::new(1_000_000.0);
|
||||
|
||||
account.open("IF2501", FuturesDirection::Long, spec, 2, 4000.0, 0.0);
|
||||
account.open("IF2501", FuturesDirection::Short, spec, 1, 4000.0, 0.0);
|
||||
|
||||
let report = account.expire_contract(d(2025, 1, 17), "IF2501", 4010.0, "contract expired");
|
||||
|
||||
assert_eq!(report.order_events.len(), 2);
|
||||
assert_eq!(report.fill_events.len(), 2);
|
||||
assert!(
|
||||
report
|
||||
.order_events
|
||||
.iter()
|
||||
.all(|event| event.status == OrderStatus::Filled)
|
||||
);
|
||||
assert!(account.position("IF2501", FuturesDirection::Long).is_none());
|
||||
assert!(
|
||||
account
|
||||
.position("IF2501", FuturesDirection::Short)
|
||||
.is_none()
|
||||
);
|
||||
assert!((account.total_cash() - 1_003_000.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,9 @@ current alignment pass.
|
||||
`accounts`)
|
||||
- [x] wire futures order intents into the generic `BacktestEngine` execution
|
||||
loop for account-level open/close execution
|
||||
- [ ] futures intraday matching integration and expiration settlement
|
||||
- [x] standalone futures expiration settlement closes all long/short contract
|
||||
positions at settlement price
|
||||
- [ ] futures intraday matching integration and data-driven expiration schedule
|
||||
|
||||
## Execution Order
|
||||
|
||||
@@ -115,4 +117,4 @@ account runtime view, core Portfolio fields, deposit/withdraw, financing
|
||||
liability APIs, management-fee callbacks, stock account accessors, and the
|
||||
standalone futures account/order execution model plus generic engine runtime
|
||||
account visibility and account-level futures order intents; next gap is adding
|
||||
futures intraday matching and expiration settlement semantics.
|
||||
futures intraday matching and a data-driven expiration schedule.
|
||||
|
||||
Reference in New Issue
Block a user