diff --git a/crates/fidc-core/src/engine.rs b/crates/fidc-core/src/engine.rs index 5857d32..4e908ee 100644 --- a/crates/fidc-core/src/engine.rs +++ b/crates/fidc-core/src/engine.rs @@ -1538,6 +1538,7 @@ where .position_events .extend(report.position_events.clone()); result.account_events.extend(report.account_events.clone()); + result.process_events.extend(report.process_events.clone()); report } @@ -2284,6 +2285,7 @@ fn merge_futures_report(target: &mut BrokerExecutionReport, incoming: FuturesExe target.fill_events.extend(incoming.fill_events); target.position_events.extend(incoming.position_events); target.account_events.extend(incoming.account_events); + target.process_events.extend(incoming.process_events); target.diagnostics.extend(incoming.diagnostics); } diff --git a/crates/fidc-core/src/futures.rs b/crates/fidc-core/src/futures.rs index 613488f..de9918e 100644 --- a/crates/fidc-core/src/futures.rs +++ b/crates/fidc-core/src/futures.rs @@ -2,7 +2,10 @@ use std::collections::BTreeMap; use chrono::NaiveDate; -use crate::events::{AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent}; +use crate::events::{ + AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent, + ProcessEventKind, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum FuturesDirection { @@ -137,6 +140,7 @@ pub struct FuturesExecutionReport { pub fill_events: Vec, pub position_events: Vec, pub account_events: Vec, + pub process_events: Vec, pub diagnostics: Vec, } @@ -514,8 +518,32 @@ impl FuturesAccountState { ) -> FuturesExecutionReport { let mut report = FuturesExecutionReport::default(); let side = intent.side(); + push_futures_process_event( + &mut report, + date, + ProcessEventKind::OrderPendingNew, + order_id, + &intent.symbol, + side, + format!( + "requested_quantity={} direction={} effect={} reason={}", + intent.quantity, + intent.direction.as_str(), + intent.effect.as_str(), + intent.reason + ), + ); if intent.quantity == 0 || !intent.price.is_finite() || intent.price <= 0.0 { + push_futures_process_event( + &mut report, + date, + ProcessEventKind::OrderCreationReject, + order_id, + &intent.symbol, + side, + "invalid futures order", + ); report.order_events.push(OrderEvent { date, order_id, @@ -611,6 +639,24 @@ impl FuturesAccountState { intent.effect.as_str() ), }); + push_futures_process_event( + &mut report, + date, + ProcessEventKind::OrderCreationPass, + order_id, + &intent.symbol, + side, + "futures order passed account checks", + ); + push_futures_process_event( + &mut report, + date, + ProcessEventKind::Trade, + order_id, + &intent.symbol, + side, + format!("filled_quantity={} price={}", intent.quantity, intent.price), + ); report.position_events.push(PositionEvent { date, symbol: intent.symbol.clone(), @@ -658,6 +704,15 @@ impl FuturesAccountState { }); } Err(reason) => { + push_futures_process_event( + &mut report, + date, + ProcessEventKind::OrderCreationReject, + order_id, + &intent.symbol, + side, + reason.clone(), + ); report.order_events.push(OrderEvent { date, order_id, @@ -725,6 +780,7 @@ impl FuturesAccountState { combined.fill_events.extend(report.fill_events); combined.position_events.extend(report.position_events); combined.account_events.extend(report.account_events); + combined.process_events.extend(report.process_events); combined.diagnostics.extend(report.diagnostics); } combined.diagnostics.push(format!( @@ -759,3 +815,22 @@ impl FuturesAccountState { cash_delta } } + +fn push_futures_process_event( + report: &mut FuturesExecutionReport, + date: NaiveDate, + kind: ProcessEventKind, + order_id: Option, + symbol: &str, + side: OrderSide, + detail: impl Into, +) { + report.process_events.push(ProcessEvent { + date, + kind, + order_id, + symbol: Some(symbol.to_string()), + side: Some(side), + detail: detail.into(), + }); +} diff --git a/crates/fidc-core/tests/engine_hooks.rs b/crates/fidc-core/tests/engine_hooks.rs index 52dea4c..d36d860 100644 --- a/crates/fidc-core/tests/engine_hooks.rs +++ b/crates/fidc-core/tests/engine_hooks.rs @@ -971,6 +971,9 @@ fn engine_executes_futures_order_intents_against_future_account() { assert!(result.fills.iter().any(|fill| { fill.symbol == "IF2501" && fill.quantity == 1 && (fill.commission - 12.0).abs() < 1e-6 })); + assert!(result.process_events.iter().any(|event| { + event.symbol.as_deref() == Some("IF2501") && event.kind == ProcessEventKind::Trade + })); let futures_account = engine.futures_account().expect("future account"); let position = futures_account .position("IF2501", FuturesDirection::Long) diff --git a/crates/fidc-core/tests/futures_account.rs b/crates/fidc-core/tests/futures_account.rs index 4554453..a2a28c6 100644 --- a/crates/fidc-core/tests/futures_account.rs +++ b/crates/fidc-core/tests/futures_account.rs @@ -85,6 +85,12 @@ fn futures_order_execution_splits_close_between_old_and_today_quantity() { assert_eq!(report.order_events.len(), 1); assert_eq!(report.order_events[0].status, OrderStatus::Filled); + assert!( + report + .process_events + .iter() + .any(|event| event.kind == fidc_core::ProcessEventKind::Trade) + ); assert_eq!(report.fill_events[0].quantity, 4); assert!((report.fill_events[0].net_cash_flow - 19_196.0).abs() < 1e-6); let position = account @@ -120,6 +126,12 @@ fn futures_close_today_rejects_when_today_quantity_is_insufficient() { assert_eq!(report.order_events.len(), 1); assert_eq!(report.order_events[0].status, OrderStatus::Rejected); + assert!( + report + .process_events + .iter() + .any(|event| event.kind == fidc_core::ProcessEventKind::OrderCreationReject) + ); assert!( report.order_events[0] .reason @@ -174,6 +186,14 @@ fn futures_expiration_settlement_closes_all_contract_directions() { assert_eq!(report.order_events.len(), 2); assert_eq!(report.fill_events.len(), 2); + assert_eq!( + report + .process_events + .iter() + .filter(|event| event.kind == fidc_core::ProcessEventKind::Trade) + .count(), + 2 + ); assert!( report .order_events