From e0a5d0c94577ed58678bfa1ff3ba3967cad902dc Mon Sep 17 00:00:00 2001 From: boris Date: Thu, 23 Apr 2026 20:03:49 -0700 Subject: [PATCH] Expose account runtime metrics --- crates/fidc-core/src/lib.rs | 4 +- .../fidc-core/src/platform_expr_strategy.rs | 96 +++++++++++++-- crates/fidc-core/src/portfolio.rs | 113 ++++++++++++++++++ crates/fidc-core/src/strategy.rs | 71 +++++++++++ crates/fidc-core/src/strategy_ai.rs | 5 +- crates/fidc-core/tests/engine_hooks.rs | 78 +++++++++++- docs/rqalpha-gap-roadmap.md | 19 ++- 7 files changed, 367 insertions(+), 19 deletions(-) diff --git a/crates/fidc-core/src/lib.rs b/crates/fidc-core/src/lib.rs index 0715138..c2651f4 100644 --- a/crates/fidc-core/src/lib.rs +++ b/crates/fidc-core/src/lib.rs @@ -46,8 +46,8 @@ pub use scheduler::{ }; pub use strategy::{ AlgoOrderStyle, CnSmallCapRotationConfig, CnSmallCapRotationStrategy, JqMicroCapConfig, - JqMicroCapStrategy, OpenOrderView, OrderIntent, OrderRuntimeView, Strategy, StrategyContext, - StrategyDecision, TargetPortfolioOrderPricing, + JqMicroCapStrategy, OpenOrderView, OrderIntent, OrderRuntimeView, PortfolioRuntimeView, + Strategy, StrategyContext, StrategyDecision, TargetPortfolioOrderPricing, }; pub use strategy_ai::{ ManualExample, ManualFactorSource, ManualField, ManualFieldGroup, ManualFunction, diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 270fdad..152317c 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -281,8 +281,22 @@ struct DayExpressionState { signal_ma20: f64, signal_ma30: f64, cash: f64, + available_cash: f64, + frozen_cash: f64, market_value: f64, total_equity: f64, + total_value: f64, + portfolio_value: f64, + starting_cash: f64, + unit_net_value: f64, + static_unit_net_value: f64, + daily_pnl: f64, + daily_returns: f64, + total_returns: f64, + transaction_cost: f64, + trading_pnl: f64, + position_pnl: f64, + cash_liabilities: f64, current_exposure: f64, position_count: i64, max_positions: i64, @@ -455,8 +469,18 @@ impl PlatformExprStrategy { "benchmark_ma_long", "cash", "available_cash", + "frozen_cash", "market_value", "total_equity", + "total_value", + "portfolio_value", + "starting_cash", + "unit_net_value", + "static_unit_net_value", + "daily_pnl", + "daily_returns", + "total_returns", + "cash_liabilities", "current_exposure", "position_count", "max_positions", @@ -1033,14 +1057,10 @@ impl PlatformExprStrategy { .data .market_decision_close_moving_average(date, &self.config.signal_symbol, 30) .unwrap_or(benchmark_ma20); - let cash = ctx.portfolio.cash(); - let market_value = ctx - .portfolio - .positions() - .values() - .map(|position| position.market_value()) - .sum::(); - let total_equity = cash + market_value; + let account = ctx.account(); + let cash = account.cash; + let market_value = account.market_value; + let total_equity = account.total_equity; let current_exposure = if total_equity > 0.0 { market_value / total_equity } else { @@ -1066,8 +1086,22 @@ impl PlatformExprStrategy { signal_ma20: benchmark_ma20, signal_ma30: benchmark_ma30, cash, + available_cash: account.available_cash, + frozen_cash: account.frozen_cash, market_value, total_equity, + total_value: account.total_value, + portfolio_value: account.portfolio_value, + starting_cash: account.starting_cash, + unit_net_value: account.unit_net_value, + static_unit_net_value: account.static_unit_net_value, + daily_pnl: account.daily_pnl, + daily_returns: account.daily_returns, + total_returns: account.total_returns, + transaction_cost: account.transaction_cost, + trading_pnl: account.trading_pnl, + position_pnl: account.position_pnl, + cash_liabilities: account.cash_liabilities, current_exposure, position_count: ctx.portfolio.positions().len() as i64, max_positions: self.config.max_positions as i64, @@ -1231,9 +1265,22 @@ impl PlatformExprStrategy { scope.push("benchmark_ma_short", day.benchmark_ma_short); scope.push("benchmark_ma_long", day.benchmark_ma_long); scope.push("cash", day.cash); - scope.push("available_cash", day.cash); + scope.push("available_cash", day.available_cash); + scope.push("frozen_cash", day.frozen_cash); scope.push("market_value", day.market_value); scope.push("total_equity", day.total_equity); + scope.push("total_value", day.total_value); + scope.push("portfolio_value", day.portfolio_value); + scope.push("starting_cash", day.starting_cash); + scope.push("unit_net_value", day.unit_net_value); + scope.push("static_unit_net_value", day.static_unit_net_value); + scope.push("daily_pnl", day.daily_pnl); + scope.push("daily_returns", day.daily_returns); + scope.push("total_returns", day.total_returns); + scope.push("transaction_cost", day.transaction_cost); + scope.push("trading_pnl", day.trading_pnl); + scope.push("position_pnl", day.position_pnl); + scope.push("cash_liabilities", day.cash_liabilities); scope.push("current_exposure", day.current_exposure); scope.push("position_count", day.position_count); scope.push("max_positions", day.max_positions); @@ -1343,8 +1390,31 @@ impl PlatformExprStrategy { Dynamic::from(day.benchmark_ma_long), ); day_factors.insert("cash".into(), Dynamic::from(day.cash)); + day_factors.insert("available_cash".into(), Dynamic::from(day.available_cash)); + day_factors.insert("frozen_cash".into(), Dynamic::from(day.frozen_cash)); day_factors.insert("market_value".into(), Dynamic::from(day.market_value)); day_factors.insert("total_equity".into(), Dynamic::from(day.total_equity)); + day_factors.insert("total_value".into(), Dynamic::from(day.total_value)); + day_factors.insert("portfolio_value".into(), Dynamic::from(day.portfolio_value)); + day_factors.insert("starting_cash".into(), Dynamic::from(day.starting_cash)); + day_factors.insert("unit_net_value".into(), Dynamic::from(day.unit_net_value)); + day_factors.insert( + "static_unit_net_value".into(), + Dynamic::from(day.static_unit_net_value), + ); + day_factors.insert("daily_pnl".into(), Dynamic::from(day.daily_pnl)); + day_factors.insert("daily_returns".into(), Dynamic::from(day.daily_returns)); + day_factors.insert("total_returns".into(), Dynamic::from(day.total_returns)); + day_factors.insert( + "transaction_cost".into(), + Dynamic::from(day.transaction_cost), + ); + day_factors.insert("trading_pnl".into(), Dynamic::from(day.trading_pnl)); + day_factors.insert("position_pnl".into(), Dynamic::from(day.position_pnl)); + day_factors.insert( + "cash_liabilities".into(), + Dynamic::from(day.cash_liabilities), + ); day_factors.insert( "current_exposure".into(), Dynamic::from(day.current_exposure), @@ -5267,7 +5337,13 @@ mod tests { " && transaction_cost == 2.0 && position_market_value > 0.0", " && value_percent > 0.0 && unrealized_pnl > 0.0", " && realized_pnl > 0.0 && pnl > 0.0", - " && day_trade_quantity_delta == 50 && trading_pnl > 90.0" + " && day_trade_quantity_delta == 50 && trading_pnl > 90.0", + " && available_cash == cash && frozen_cash == 0.0", + " && total_value == total_equity && portfolio_value == total_equity", + " && starting_cash == 1000000.0 && unit_net_value > 1.0", + " && static_unit_net_value > 1.0 && daily_pnl > 290.0", + " && daily_returns > 0.0 && total_returns > 0.0", + " && cash_liabilities == 0.0" ) .to_string(); let mut strategy = PlatformExprStrategy::new(cfg); diff --git a/crates/fidc-core/src/portfolio.rs b/crates/fidc-core/src/portfolio.rs index 3cb23d3..d412a19 100644 --- a/crates/fidc-core/src/portfolio.rs +++ b/crates/fidc-core/src/portfolio.rs @@ -307,6 +307,7 @@ impl Position { #[derive(Debug, Clone)] pub struct PortfolioState { + initial_cash: f64, cash: f64, positions: IndexMap, cash_receivables: Vec, @@ -326,12 +327,21 @@ pub(crate) struct SuccessorConversionOutcome { impl PortfolioState { pub fn new(initial_cash: f64) -> Self { Self { + initial_cash, cash: initial_cash, positions: IndexMap::new(), cash_receivables: Vec::new(), } } + pub fn starting_cash(&self) -> f64 { + self.initial_cash + } + + pub fn units(&self) -> f64 { + self.initial_cash + } + pub fn cash(&self) -> f64 { self.cash } @@ -423,10 +433,72 @@ impl PortfolioState { self.positions.values().map(Position::market_value).sum() } + pub fn transaction_cost(&self) -> f64 { + self.positions + .values() + .map(Position::transaction_cost) + .sum() + } + + pub fn trading_pnl(&self) -> f64 { + self.positions + .values() + .map(|position| position.trading_pnl) + .sum() + } + + pub fn position_pnl(&self) -> f64 { + self.positions + .values() + .map(|position| position.position_pnl) + .sum() + } + + pub fn daily_pnl(&self) -> f64 { + self.trading_pnl() + self.position_pnl() + } + pub fn total_equity(&self) -> f64 { self.cash + self.market_value() } + pub fn total_value(&self) -> f64 { + self.total_equity() + } + + pub fn portfolio_value(&self) -> f64 { + self.total_equity() + } + + pub fn unit_net_value(&self) -> f64 { + if self.initial_cash.abs() < f64::EPSILON { + 0.0 + } else { + self.total_equity() / self.initial_cash + } + } + + pub fn static_unit_net_value(&self) -> f64 { + if self.initial_cash.abs() < f64::EPSILON { + 0.0 + } else { + (self.total_equity() - self.daily_pnl()) / self.initial_cash + } + } + + pub fn daily_returns(&self) -> f64 { + let previous_value = self.total_equity() - self.daily_pnl(); + if previous_value.abs() < f64::EPSILON { + 0.0 + } else { + self.daily_pnl() / previous_value + } + } + + pub fn total_returns(&self) -> f64 { + self.unit_net_value() - 1.0 + } + pub fn holdings_summary(&self, date: NaiveDate) -> Vec { let total_equity = self.total_equity(); self.positions @@ -819,6 +891,47 @@ mod tests { assert!((summary[0].transaction_cost - 3.0).abs() < 1e-6); assert!(summary[0].value_percent > 0.0); } + + #[test] + fn portfolio_exposes_rqalpha_style_account_metrics() { + let prev_date = NaiveDate::from_ymd_opt(2025, 1, 2).unwrap(); + let date = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + let mut portfolio = PortfolioState::new(10_000.0); + portfolio + .position_mut("000001.SZ") + .buy(prev_date, 100, 10.0); + portfolio.begin_trading_day(); + portfolio.position_mut("000001.SZ").buy(date, 50, 11.0); + portfolio + .position_mut("000001.SZ") + .sell(40, 12.0) + .expect("sell"); + portfolio.position_mut("000001.SZ").record_trade_cost(3.0); + + assert!((portfolio.starting_cash() - 10_000.0).abs() < 1e-6); + assert!((portfolio.units() - 10_000.0).abs() < 1e-6); + assert!((portfolio.transaction_cost() - 3.0).abs() < 1e-6); + assert!((portfolio.trading_pnl() - 47.0).abs() < 1e-6); + assert!((portfolio.position_pnl() - 200.0).abs() < 1e-6); + assert!((portfolio.daily_pnl() - 247.0).abs() < 1e-6); + assert!((portfolio.total_value() - portfolio.total_equity()).abs() < 1e-6); + assert!((portfolio.portfolio_value() - portfolio.total_equity()).abs() < 1e-6); + assert!((portfolio.unit_net_value() - portfolio.total_equity() / 10_000.0).abs() < 1e-6); + assert!( + (portfolio.static_unit_net_value() + - (portfolio.total_equity() - portfolio.daily_pnl()) / 10_000.0) + .abs() + < 1e-6 + ); + assert!( + (portfolio.daily_returns() + - portfolio.daily_pnl() / (portfolio.total_equity() - portfolio.daily_pnl())) + .abs() + < 1e-6 + ); + assert!((portfolio.total_returns() - (portfolio.unit_net_value() - 1.0)).abs() < 1e-6); + assert_eq!(portfolio.cash_receivables().len(), 0); + } } #[derive(Debug, Clone, Serialize)] diff --git a/crates/fidc-core/src/strategy.rs b/crates/fidc-core/src/strategy.rs index 1842df1..7c6ee8d 100644 --- a/crates/fidc-core/src/strategy.rs +++ b/crates/fidc-core/src/strategy.rs @@ -95,6 +95,28 @@ pub struct OrderRuntimeView { pub reason: String, } +#[derive(Debug, Clone)] +pub struct PortfolioRuntimeView { + pub starting_cash: f64, + pub units: f64, + pub cash: f64, + pub available_cash: f64, + pub frozen_cash: f64, + pub market_value: f64, + pub total_value: f64, + pub portfolio_value: f64, + pub total_equity: f64, + pub unit_net_value: f64, + pub static_unit_net_value: f64, + pub daily_pnl: f64, + pub daily_returns: f64, + pub total_returns: f64, + pub transaction_cost: f64, + pub trading_pnl: f64, + pub position_pnl: f64, + pub cash_liabilities: f64, +} + pub struct StrategyContext<'a> { pub execution_date: NaiveDate, pub decision_date: NaiveDate, @@ -323,6 +345,55 @@ impl StrategyContext<'_> { .unwrap_or(0.0) } + pub fn portfolio_view(&self) -> PortfolioRuntimeView { + let frozen_cash = self.frozen_cash(); + let cash = self.portfolio.cash(); + let total_equity = self.portfolio.total_equity(); + PortfolioRuntimeView { + starting_cash: self.portfolio.starting_cash(), + units: self.portfolio.units(), + cash, + available_cash: (cash - frozen_cash).max(0.0), + frozen_cash, + market_value: self.portfolio.market_value(), + total_value: self.portfolio.total_value(), + portfolio_value: self.portfolio.portfolio_value(), + total_equity, + unit_net_value: self.portfolio.unit_net_value(), + static_unit_net_value: self.portfolio.static_unit_net_value(), + daily_pnl: self.portfolio.daily_pnl(), + daily_returns: self.portfolio.daily_returns(), + total_returns: self.portfolio.total_returns(), + transaction_cost: self.portfolio.transaction_cost(), + trading_pnl: self.portfolio.trading_pnl(), + position_pnl: self.portfolio.position_pnl(), + cash_liabilities: 0.0, + } + } + + pub fn account(&self) -> PortfolioRuntimeView { + self.portfolio_view() + } + + pub fn frozen_cash(&self) -> f64 { + self.open_orders + .iter() + .filter(|order| order.side == OrderSide::Buy) + .map(|order| { + let price = if order.limit_price.is_finite() { + order.limit_price.max(0.0) + } else { + 0.0 + }; + order.remaining_quantity as f64 * price + }) + .sum() + } + + pub fn available_cash(&self) -> f64 { + (self.portfolio.cash() - self.frozen_cash()).max(0.0) + } + pub fn available_sellable_qty(&self, symbol: &str, raw_sellable_qty: u32) -> u32 { raw_sellable_qty.saturating_sub(self.symbol_open_sell_quantity(symbol)) } diff --git a/crates/fidc-core/src/strategy_ai.rs b/crates/fidc-core/src/strategy_ai.rs index a085669..d9e0cad 100644 --- a/crates/fidc-core/src/strategy_ai.rs +++ b/crates/fidc-core/src/strategy_ai.rs @@ -140,7 +140,9 @@ pub fn built_in_strategy_manual() -> StrategyAiManual { ManualField { name: "benchmark_open/benchmark_close".to_string(), field_type: "float".to_string(), detail: "基准当日开盘价与前一日收盘价。".to_string() }, ManualField { name: "signal_ma5/signal_ma10/signal_ma20/signal_ma30".to_string(), field_type: "float".to_string(), detail: "信号指数滚动均线。".to_string() }, ManualField { name: "benchmark_ma5/benchmark_ma10/benchmark_ma20/benchmark_ma30".to_string(), field_type: "float".to_string(), detail: "基准指数滚动均线。".to_string() }, - ManualField { name: "cash/available_cash/market_value/total_equity".to_string(), field_type: "float".to_string(), detail: "账户资金与总资产。".to_string() }, + ManualField { name: "cash/available_cash/frozen_cash/market_value/total_equity".to_string(), field_type: "float".to_string(), detail: "账户可用资金、挂单冻结资金、市值与总权益;available_cash 会扣减当前买入挂单冻结估算。".to_string() }, + ManualField { name: "total_value/portfolio_value/starting_cash/unit_net_value/static_unit_net_value".to_string(), field_type: "float".to_string(), detail: "组合总权益别名、初始资金、实时净值和昨日静态净值,对齐 RQAlpha Portfolio 常用字段。".to_string() }, + ManualField { name: "daily_pnl/daily_returns/total_returns/transaction_cost/trading_pnl/position_pnl/cash_liabilities".to_string(), field_type: "float".to_string(), detail: "账户当日盈亏、日收益率、累计收益率、当日交易成本、交易盈亏、持仓盈亏和现金负债;股票账户现金负债默认为 0。".to_string() }, ManualField { name: "position_count/max_positions/refresh_rate".to_string(), field_type: "int".to_string(), detail: "仓位计数与调仓周期。".to_string() }, ManualField { name: "has_open_orders/open_order_count/open_buy_order_count/open_sell_order_count".to_string(), field_type: "bool/int".to_string(), detail: "当前阶段挂单簿摘要。".to_string() }, ManualField { name: "open_buy_qty/open_sell_qty/latest_open_order_id".to_string(), field_type: "int".to_string(), detail: "当前阶段未成交买卖挂单的剩余数量汇总,以及最近一笔挂单 id。".to_string() }, @@ -205,6 +207,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual { ManualFunction { name: "is_suspended/is_st_stock".to_string(), signature: "ctx.is_suspended(symbol, count)".to_string(), detail: "读取指定证券截至当前交易日最近 count 个交易日的停牌或 ST 标记,返回 bool 序列,顺序从旧到新;对应 RQAlpha 的 is_suspended/is_st_stock 数据源能力。".to_string() }, ManualFunction { name: "get_price".to_string(), signature: "ctx.get_price(symbol, start_date, end_date, \"1d\" | \"1m\" | \"tick\")".to_string(), detail: "按日期区间读取统一 PriceBar 序列。日线返回 open/high/low/close/last/volume/盘口字段;分钟或 tick 返回按 timestamp 排序的 last/bid1/ask1/volume_delta/amount_delta 映射,便于服务层转成表格或前端明细。".to_string() }, ManualFunction { name: "order/order_status/order_avg_price/order_transaction_cost".to_string(), signature: "ctx.order(order_id)".to_string(), detail: "按订单 id 查询运行时订单对象,支持已结束订单和当前挂单。返回字段包括 status、filled_quantity、unfilled_quantity、avg_price、transaction_cost、symbol、side、reason;可用便捷函数读取状态、成交均价和费用,对齐 RQAlpha Order 的核心属性。".to_string() }, + ManualFunction { name: "account/portfolio_view".to_string(), signature: "ctx.account()".to_string(), detail: "返回当前股票账户/组合运行时视图,字段包括 cash、available_cash、frozen_cash、market_value、total_value、unit_net_value、daily_pnl、daily_returns、total_returns、transaction_cost、trading_pnl、position_pnl 等;DSL 中同名字段可直接使用。".to_string() }, ManualFunction { name: "rolling_mean".to_string(), signature: "rolling_mean(\"field\", lookback)".to_string(), detail: "任意字段滚动均值,支持 volume/amount/turnover_ratio、signal_open/signal_close、benchmark_open/benchmark_close 等。任意成交量窗口推荐用它,比如 rolling_mean(\"volume\", 15)。".to_string() }, ManualFunction { name: "sma".to_string(), signature: "sma(\"field\", lookback)".to_string(), detail: "rolling_mean 的别名。任意价格均线窗口推荐用它,比如 sma(\"close\", 15)。".to_string() }, ManualFunction { name: "round/floor/ceil/abs/min/max/clamp".to_string(), signature: "round(x)".to_string(), detail: "常用数值函数。".to_string() }, diff --git a/crates/fidc-core/tests/engine_hooks.rs b/crates/fidc-core/tests/engine_hooks.rs index 2819647..6e77624 100644 --- a/crates/fidc-core/tests/engine_hooks.rs +++ b/crates/fidc-core/tests/engine_hooks.rs @@ -6,8 +6,9 @@ use chrono::{NaiveDate, NaiveDateTime}; use fidc_core::{ BacktestConfig, BacktestEngine, BenchmarkSnapshot, BrokerSimulator, CandidateEligibility, ChinaAShareCostModel, ChinaEquityRuleHooks, DailyFactorSnapshot, DailyMarketSnapshot, DataSet, - Instrument, IntradayExecutionQuote, OrderIntent, PriceField, ProcessEventKind, ScheduleRule, - ScheduleStage, ScheduleTimeRule, Strategy, StrategyContext, StrategyDecision, + Instrument, IntradayExecutionQuote, OpenOrderView, OrderIntent, OrderSide, OrderStatus, + PortfolioState, PriceField, ProcessEventKind, ScheduleRule, ScheduleStage, ScheduleTimeRule, + Strategy, StrategyContext, StrategyDecision, }; fn d(year: i32, month: u32, day: u32) -> NaiveDate { @@ -1226,6 +1227,79 @@ fn strategy_context_exposes_final_order_runtime_view() { ); } +#[test] +fn strategy_context_exposes_rqalpha_style_account_runtime_view() { + let prev_date = d(2025, 1, 2); + let date = d(2025, 1, 3); + let data = DataSet::from_components( + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + vec![BenchmarkSnapshot { + date, + benchmark: "000300.SH".to_string(), + open: 100.0, + close: 100.0, + prev_close: 99.0, + volume: 1_000_000, + }], + ) + .expect("dataset"); + let mut portfolio = PortfolioState::new(10_000.0); + portfolio + .position_mut("000001.SZ") + .buy(prev_date, 100, 10.0); + portfolio.begin_trading_day(); + portfolio.position_mut("000001.SZ").buy(date, 50, 11.0); + portfolio + .position_mut("000001.SZ") + .sell(40, 12.0) + .expect("sell"); + portfolio.position_mut("000001.SZ").record_trade_cost(3.0); + let open_orders = vec![OpenOrderView { + order_id: 7, + symbol: "000002.SZ".to_string(), + side: OrderSide::Buy, + requested_quantity: 100, + filled_quantity: 0, + remaining_quantity: 50, + unfilled_quantity: 50, + status: OrderStatus::Pending, + avg_price: 0.0, + transaction_cost: 0.0, + limit_price: 12.0, + reason: "pending_buy".to_string(), + }]; + let subscriptions = BTreeSet::new(); + let ctx = StrategyContext { + execution_date: date, + decision_date: date, + decision_index: 0, + data: &data, + portfolio: &portfolio, + open_orders: &open_orders, + dynamic_universe: None, + subscriptions: &subscriptions, + process_events: &[], + active_process_event: None, + active_datetime: None, + order_events: &[], + fills: &[], + }; + + let account = ctx.account(); + + assert!((account.starting_cash - 10_000.0).abs() < 1e-6); + assert!((account.frozen_cash - 600.0).abs() < 1e-6); + assert!((account.available_cash - 9_400.0).abs() < 1e-6); + assert!((account.transaction_cost - 3.0).abs() < 1e-6); + assert!((account.daily_pnl - 247.0).abs() < 1e-6); + assert!((account.daily_returns - portfolio.daily_returns()).abs() < 1e-6); + assert!((account.total_returns - portfolio.total_returns()).abs() < 1e-6); + assert!((ctx.available_cash() - account.available_cash).abs() < 1e-6); +} + #[test] fn engine_rejects_pending_limit_orders_at_market_close() { let date1 = d(2025, 1, 2); diff --git a/docs/rqalpha-gap-roadmap.md b/docs/rqalpha-gap-roadmap.md index b8e33f5..b3e1bad 100644 --- a/docs/rqalpha-gap-roadmap.md +++ b/docs/rqalpha-gap-roadmap.md @@ -70,6 +70,17 @@ current alignment pass. - [x] final order object lookup by order id - [x] order average fill price and transaction cost aggregation +### Phase 9: Account / portfolio API parity + +- [x] stock-account runtime view (`ctx.account()` / `ctx.portfolio_view()`) +- [x] `cash`, `available_cash`, `frozen_cash`, `market_value`, `total_value` + exposed to strategy runtime and DSL +- [x] `unit_net_value`, `static_unit_net_value`, `daily_pnl`, + `daily_returns`, `total_returns`, `transaction_cost`, `trading_pnl`, + and `position_pnl` exposed to strategy runtime and DSL +- [ ] explicit deposit / withdraw API +- [ ] financing liability / repay API + ## Execution Order 1. Close the explicit order API gap with target-shares / `order_to` parity. @@ -80,10 +91,10 @@ current alignment pass. 6. Finish position accounting parity. 7. Continue stock data-source API parity. 8. Continue order object API parity. -9. Continue parity audit for remaining account APIs. +9. Continue account / portfolio API parity. ## Current Step -Active implementation target: continue parity audit for remaining account APIs -after order object lookup, status, unfilled quantity, average fill price, and -transaction cost aggregation are covered. +Active implementation target: continue account parity after exposing the stock +account runtime view and core Portfolio fields; next gaps are explicit +deposit/withdraw and financing liability APIs.