From db4e385308067ab5e2129e94b64e6cc385ee5f65 Mon Sep 17 00:00:00 2001 From: boris Date: Thu, 23 Apr 2026 20:35:32 -0700 Subject: [PATCH] Expose futures account runtime view --- crates/fidc-core/src/engine.rs | 77 +++++++++++++++++++ crates/fidc-core/src/futures.rs | 6 ++ .../fidc-core/src/platform_expr_strategy.rs | 13 ++++ crates/fidc-core/src/strategy.rs | 50 +++++++++++- crates/fidc-core/tests/engine_hooks.rs | 41 +++++++++- crates/fidc-core/tests/strategy_selection.rs | 2 + docs/rqalpha-gap-roadmap.md | 11 ++- 7 files changed, 191 insertions(+), 9 deletions(-) diff --git a/crates/fidc-core/src/engine.rs b/crates/fidc-core/src/engine.rs index db7fe2b..5d628ad 100644 --- a/crates/fidc-core/src/engine.rs +++ b/crates/fidc-core/src/engine.rs @@ -12,6 +12,7 @@ use crate::events::{ AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent, ProcessEventKind, }; +use crate::futures::FuturesAccountState; use crate::metrics::{BacktestMetrics, compute_backtest_metrics}; use crate::portfolio::{CashReceivable, HoldingSummary, PortfolioState}; use crate::rules::EquityRuleHooks; @@ -101,6 +102,7 @@ pub struct BacktestEngine { process_event_bus: ProcessEventBus, dynamic_universe: Option>, subscriptions: BTreeSet, + futures_account: Option, } impl BacktestEngine { @@ -119,6 +121,7 @@ impl BacktestEngine { process_event_bus: ProcessEventBus::new(), dynamic_universe: None, subscriptions: BTreeSet::new(), + futures_account: None, } } @@ -127,6 +130,23 @@ impl BacktestEngine { self } + pub fn with_futures_account(mut self, account: FuturesAccountState) -> Self { + self.futures_account = Some(account); + self + } + + pub fn with_futures_initial_cash(self, initial_cash: f64) -> Self { + self.with_futures_account(FuturesAccountState::new(initial_cash)) + } + + pub fn futures_account(&self) -> Option<&FuturesAccountState> { + self.futures_account.as_ref() + } + + pub fn futures_account_mut(&mut self) -> Option<&mut FuturesAccountState> { + self.futures_account.as_mut() + } + pub fn process_event_bus_mut(&mut self) -> &mut ProcessEventBus { &mut self.process_event_bus } @@ -184,6 +204,7 @@ where decision_index, &self.data, portfolio, + self.futures_account.as_ref(), open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -224,6 +245,7 @@ where decision_index, &self.data, portfolio, + self.futures_account.as_ref(), open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -264,6 +286,7 @@ where decision_index, &self.data, portfolio, + self.futures_account.as_ref(), open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -333,6 +356,7 @@ where decision_index, &self.data, &*portfolio, + self.futures_account.as_ref(), open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -378,6 +402,7 @@ where decision_index, &self.data, &*portfolio, + self.futures_account.as_ref(), open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -411,6 +436,7 @@ where decision_index, &self.data, &*portfolio, + self.futures_account.as_ref(), open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -540,6 +566,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &pre_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -554,6 +581,7 @@ where decision_index, data: &self.data, portfolio: &portfolio, + futures_account: self.futures_account.as_ref(), open_orders: &pre_open_orders, dynamic_universe: self.dynamic_universe.as_ref(), subscriptions: &self.subscriptions, @@ -574,6 +602,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &pre_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -592,6 +621,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &pre_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -619,6 +649,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &pre_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -635,6 +666,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &pre_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -653,6 +685,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &pre_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -668,6 +701,7 @@ where decision_index, data: &self.data, portfolio: &portfolio, + futures_account: self.futures_account.as_ref(), open_orders: &pre_open_orders, dynamic_universe: self.dynamic_universe.as_ref(), subscriptions: &self.subscriptions, @@ -688,6 +722,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &pre_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -721,6 +756,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_auction_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -735,6 +771,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_auction_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -752,6 +789,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_auction_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -769,6 +807,7 @@ where decision_index: decision_idx, data: &self.data, portfolio: &portfolio, + futures_account: self.futures_account.as_ref(), open_orders: &on_day_open_orders, dynamic_universe: self.dynamic_universe.as_ref(), subscriptions: &self.subscriptions, @@ -794,6 +833,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &on_day_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -811,6 +851,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &on_day_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -828,6 +869,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &bar_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -846,6 +888,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &bar_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -861,6 +904,7 @@ where decision_index, data: &self.data, portfolio: &portfolio, + futures_account: self.futures_account.as_ref(), open_orders: &bar_open_orders, dynamic_universe: self.dynamic_universe.as_ref(), subscriptions: &self.subscriptions, @@ -881,6 +925,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &bar_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -912,6 +957,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_intraday_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -933,6 +979,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_intraday_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -949,6 +996,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_intraday_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -979,6 +1027,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &tick_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -997,6 +1046,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &tick_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1013,6 +1063,7 @@ where decision_index, data: &self.data, portfolio: &portfolio, + futures_account: self.futures_account.as_ref(), open_orders: &tick_open_orders, dynamic_universe: self.dynamic_universe.as_ref(), subscriptions: &self.subscriptions, @@ -1032,6 +1083,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &tick_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1067,6 +1119,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_tick_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1082,6 +1135,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_tick_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1116,6 +1170,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_trade_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1130,6 +1185,7 @@ where decision_index, data: &self.data, portfolio: &portfolio, + futures_account: self.futures_account.as_ref(), open_orders: &post_trade_open_orders, dynamic_universe: self.dynamic_universe.as_ref(), subscriptions: &self.subscriptions, @@ -1150,6 +1206,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_trade_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1168,6 +1225,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_trade_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1196,6 +1254,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_trade_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1228,6 +1287,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_close_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1244,6 +1304,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_close_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1258,6 +1319,7 @@ where decision_index, data: &self.data, portfolio: &portfolio, + futures_account: self.futures_account.as_ref(), open_orders: &post_close_open_orders, dynamic_universe: self.dynamic_universe.as_ref(), subscriptions: &self.subscriptions, @@ -1278,6 +1340,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_close_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1296,6 +1359,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_close_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1338,6 +1402,7 @@ where decision_index, &self.data, &portfolio, + self.futures_account.as_ref(), &post_close_open_orders, self.dynamic_universe.as_ref(), &self.subscriptions, @@ -1769,6 +1834,7 @@ where decision_index, data: &self.data, portfolio, + futures_account: self.futures_account.as_ref(), open_orders, dynamic_universe, subscriptions, @@ -1808,6 +1874,7 @@ where decision_index, &self.data, &*portfolio, + self.futures_account.as_ref(), open_orders, dynamic_universe, subscriptions, @@ -1952,6 +2019,7 @@ fn collect_scheduled_decisions( decision_index: usize, data: &crate::data::DataSet, portfolio: &PortfolioState, + futures_account: Option<&FuturesAccountState>, open_orders: &[crate::strategy::OpenOrderView], dynamic_universe: Option<&BTreeSet>, subscriptions: &BTreeSet, @@ -1971,6 +2039,7 @@ fn collect_scheduled_decisions( decision_index, data, portfolio, + futures_account, open_orders, dynamic_universe, subscriptions, @@ -1986,6 +2055,7 @@ fn collect_scheduled_decisions( decision_index, data, portfolio, + futures_account, open_orders, dynamic_universe, subscriptions, @@ -2005,6 +2075,7 @@ fn collect_scheduled_decisions( decision_index, data, portfolio, + futures_account, open_orders, dynamic_universe, subscriptions, @@ -2025,6 +2096,7 @@ fn publish_phase_event( decision_index: usize, data: &crate::data::DataSet, portfolio: &PortfolioState, + futures_account: Option<&FuturesAccountState>, open_orders: &[crate::strategy::OpenOrderView], dynamic_universe: Option<&BTreeSet>, subscriptions: &BTreeSet, @@ -2049,6 +2121,7 @@ fn publish_phase_event( decision_index, data, portfolio, + futures_account, open_orders, dynamic_universe, subscriptions, @@ -2071,6 +2144,7 @@ fn publish_process_events( decision_index: usize, data: &crate::data::DataSet, portfolio: &PortfolioState, + futures_account: Option<&FuturesAccountState>, open_orders: &[crate::strategy::OpenOrderView], dynamic_universe: Option<&BTreeSet>, subscriptions: &BTreeSet, @@ -2086,6 +2160,7 @@ fn publish_process_events( decision_index, data, portfolio, + futures_account, open_orders, dynamic_universe, subscriptions, @@ -2109,6 +2184,7 @@ fn publish_custom_process_event( decision_index: usize, data: &crate::data::DataSet, portfolio: &PortfolioState, + futures_account: Option<&FuturesAccountState>, open_orders: &[crate::strategy::OpenOrderView], dynamic_universe: Option<&BTreeSet>, subscriptions: &BTreeSet, @@ -2123,6 +2199,7 @@ fn publish_custom_process_event( decision_index, data, portfolio, + futures_account, open_orders, dynamic_universe, subscriptions, diff --git a/crates/fidc-core/src/futures.rs b/crates/fidc-core/src/futures.rs index 9deab2a..066a6b0 100644 --- a/crates/fidc-core/src/futures.rs +++ b/crates/fidc-core/src/futures.rs @@ -353,6 +353,7 @@ impl FuturesPosition { #[derive(Debug, Clone)] pub struct FuturesAccountState { + starting_cash: f64, total_cash: f64, frozen_cash: f64, positions: BTreeMap<(String, FuturesDirection), FuturesPosition>, @@ -361,12 +362,17 @@ pub struct FuturesAccountState { impl FuturesAccountState { pub fn new(total_cash: f64) -> Self { Self { + starting_cash: total_cash, total_cash, frozen_cash: 0.0, positions: BTreeMap::new(), } } + pub fn starting_cash(&self) -> f64 { + self.starting_cash + } + pub fn total_cash(&self) -> f64 { self.total_cash } diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 68ac50e..8a168f3 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -4072,6 +4072,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -4231,6 +4232,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -4349,6 +4351,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -4472,6 +4475,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -4578,6 +4582,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -4679,6 +4684,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -4798,6 +4804,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -4920,6 +4927,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -5047,6 +5055,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &open_orders, dynamic_universe: None, subscriptions: &subscriptions, @@ -5184,6 +5193,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &open_orders, dynamic_universe: None, subscriptions: &subscriptions, @@ -5293,6 +5303,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -5430,6 +5441,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -5548,6 +5560,7 @@ mod tests { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, diff --git a/crates/fidc-core/src/strategy.rs b/crates/fidc-core/src/strategy.rs index c88da4c..ff3a5ae 100644 --- a/crates/fidc-core/src/strategy.rs +++ b/crates/fidc-core/src/strategy.rs @@ -10,6 +10,7 @@ use crate::cost::ChinaAShareCostModel; use crate::data::{DailyMarketSnapshot, DataSet, IntradayExecutionQuote, PriceBar, PriceField}; use crate::engine::BacktestError; use crate::events::{FillEvent, OrderEvent, OrderSide, OrderStatus, ProcessEvent}; +use crate::futures::FuturesAccountState; use crate::instrument::Instrument; use crate::portfolio::PortfolioState; use crate::scheduler::ScheduleRule; @@ -133,6 +134,7 @@ pub struct StrategyContext<'a> { pub decision_index: usize, pub data: &'a DataSet, pub portfolio: &'a PortfolioState, + pub futures_account: Option<&'a FuturesAccountState>, pub open_orders: &'a [OpenOrderView], pub dynamic_universe: Option<&'a BTreeSet>, pub subscriptions: &'a BTreeSet, @@ -393,19 +395,55 @@ impl StrategyContext<'_> { } pub fn future_account(&self) -> Option { - None + self.futures_account.map(|account| { + let starting_cash = account.starting_cash(); + let total_value = account.total_value(); + let daily_pnl = account.daily_pnl(); + let static_base = total_value - daily_pnl; + let unit_net_value = safe_ratio(total_value, starting_cash); + let static_unit_net_value = safe_ratio(static_base, starting_cash); + PortfolioRuntimeView { + account_type: "FUTURE", + starting_cash, + units: 1.0, + cash: account.cash(), + available_cash: account.cash(), + frozen_cash: account.frozen_cash(), + market_value: account.market_value(), + total_value, + portfolio_value: total_value, + total_equity: total_value, + unit_net_value, + static_unit_net_value, + daily_pnl, + daily_returns: safe_ratio(daily_pnl, static_base), + total_returns: safe_ratio(total_value - starting_cash, starting_cash), + transaction_cost: account.transaction_cost(), + trading_pnl: account.trading_pnl(), + position_pnl: account.position_pnl(), + cash_liabilities: 0.0, + management_fee_rate: 0.0, + management_fees: 0.0, + } + }) } pub fn account_by_type(&self, account_type: &str) -> Option { if account_type.eq_ignore_ascii_case("STOCK") { Some(self.stock_account()) + } else if account_type.eq_ignore_ascii_case("FUTURE") { + self.future_account() } else { None } } pub fn accounts(&self) -> BTreeMap { - BTreeMap::from([("STOCK".to_string(), self.stock_account())]) + let mut accounts = BTreeMap::from([("STOCK".to_string(), self.stock_account())]); + if let Some(future_account) = self.future_account() { + accounts.insert("FUTURE".to_string(), future_account); + } + accounts } pub fn frozen_cash(&self) -> f64 { @@ -673,6 +711,14 @@ impl StrategyContext<'_> { } } +fn safe_ratio(numerator: f64, denominator: f64) -> f64 { + if denominator.abs() <= f64::EPSILON { + 0.0 + } else { + numerator / denominator + } +} + #[derive(Debug, Clone, Default)] pub struct StrategyDecision { pub rebalance: bool, diff --git a/crates/fidc-core/tests/engine_hooks.rs b/crates/fidc-core/tests/engine_hooks.rs index 4307822..4153995 100644 --- a/crates/fidc-core/tests/engine_hooks.rs +++ b/crates/fidc-core/tests/engine_hooks.rs @@ -6,9 +6,10 @@ use chrono::{NaiveDate, NaiveDateTime}; use fidc_core::{ BacktestConfig, BacktestEngine, BenchmarkSnapshot, BrokerSimulator, CandidateEligibility, ChinaAShareCostModel, ChinaEquityRuleHooks, DailyFactorSnapshot, DailyMarketSnapshot, DataSet, - Instrument, IntradayExecutionQuote, OpenOrderView, OrderIntent, OrderSide, OrderStatus, - PortfolioState, PriceField, ProcessEventKind, ScheduleRule, ScheduleStage, ScheduleTimeRule, - Strategy, StrategyContext, StrategyDecision, + FuturesAccountState, FuturesContractSpec, FuturesDirection, Instrument, IntradayExecutionQuote, + OpenOrderView, OrderIntent, OrderSide, OrderStatus, PortfolioState, PriceField, + ProcessEventKind, ScheduleRule, ScheduleStage, ScheduleTimeRule, Strategy, StrategyContext, + StrategyDecision, }; fn d(year: i32, month: u32, day: u32) -> NaiveDate { @@ -1330,6 +1331,7 @@ fn strategy_context_exposes_rqalpha_style_account_runtime_view() { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &open_orders, dynamic_universe: None, subscriptions: &subscriptions, @@ -1358,6 +1360,39 @@ fn strategy_context_exposes_rqalpha_style_account_runtime_view() { ctx.accounts().keys().cloned().collect::>(), vec!["STOCK".to_string()] ); + + let spec = FuturesContractSpec::new(300.0, 0.12, 0.14); + let mut futures_account = FuturesAccountState::new(500_000.0); + futures_account.open("IF2501", FuturesDirection::Long, spec, 2, 4000.0, 12.0); + futures_account.mark_price("IF2501", FuturesDirection::Long, 4010.0); + let future_ctx = StrategyContext { + execution_date: date, + decision_date: date, + decision_index: 0, + data: &data, + portfolio: &portfolio, + futures_account: Some(&futures_account), + open_orders: &open_orders, + dynamic_universe: None, + subscriptions: &subscriptions, + process_events: &[], + active_process_event: None, + active_datetime: None, + order_events: &[], + fills: &[], + }; + + let future_account = future_ctx.future_account().expect("future account"); + assert_eq!(future_account.account_type, "FUTURE"); + assert!((future_account.starting_cash - 500_000.0).abs() < 1e-6); + assert!((future_account.cash - 211_268.0).abs() < 1e-6); + assert!((future_account.market_value - 2_406_000.0).abs() < 1e-6); + assert!((future_account.total_value - 505_988.0).abs() < 1e-6); + assert!((future_ctx.account_by_type("future").unwrap().cash - 211_268.0).abs() < 1e-6); + assert_eq!( + future_ctx.accounts().keys().cloned().collect::>(), + vec!["FUTURE".to_string(), "STOCK".to_string()] + ); } #[test] diff --git a/crates/fidc-core/tests/strategy_selection.rs b/crates/fidc-core/tests/strategy_selection.rs index 9422c63..bf99411 100644 --- a/crates/fidc-core/tests/strategy_selection.rs +++ b/crates/fidc-core/tests/strategy_selection.rs @@ -27,6 +27,7 @@ fn strategy_emits_target_weights_and_diagnostics() { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, @@ -73,6 +74,7 @@ fn jq_strategy_emits_same_day_decision() { decision_index: 0, data: &data, portfolio: &portfolio, + futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions, diff --git a/docs/rqalpha-gap-roadmap.md b/docs/rqalpha-gap-roadmap.md index cf0dbbb..d503187 100644 --- a/docs/rqalpha-gap-roadmap.md +++ b/docs/rqalpha-gap-roadmap.md @@ -89,7 +89,10 @@ current alignment pass. margin, daily mark-to-market settlement, and short close cashflow - [x] standalone futures order execution model with open, close, close-today, close-yesterday, margin rejection, order/fill/position/account events -- [ ] wire futures account into the generic backtest engine runtime +- [x] wire futures account runtime view into `BacktestEngine` and + `StrategyContext` (`future_account`, `account_by_type("FUTURE")`, + `accounts`) +- [ ] wire futures order intents into the generic `BacktestEngine` execution loop - [ ] futures intraday matching integration and expiration settlement ## Execution Order @@ -109,6 +112,6 @@ current alignment pass. Active implementation target: continue account parity after exposing the stock 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; next gap is wiring futures -into the generic engine runtime and adding futures intraday/expiration -semantics. +standalone futures account/order execution model plus generic engine runtime +account visibility; next gap is wiring futures order intents into the generic +engine execution loop and adding futures intraday/expiration semantics.