diff --git a/crates/risk-data/src/var.rs b/crates/risk-data/src/var.rs index da230494f..492f84148 100644 --- a/crates/risk-data/src/var.rs +++ b/crates/risk-data/src/var.rs @@ -128,6 +128,14 @@ pub struct PortfolioPosition { pub asset_class: String, } +/// Internal row type for SQLX query mapping from `broker_positions` table +#[derive(Debug, sqlx::FromRow)] +struct PortfolioPositionRow { + symbol: String, + quantity: Decimal, + market_value: Option, +} + /// Historical price data #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct PriceData { @@ -642,10 +650,38 @@ impl VarRepository for VarRepositoryImpl { &self, portfolio_id: &str, ) -> RiskDataResult> { - // STUB: requires broker or database integration info!("Getting portfolio positions for {}", portfolio_id); - warn!("STUB: get_portfolio_positions returns empty -- wire to broker/DB for real positions"); - Ok(vec![]) + + // Query broker_positions table (populated by fill events from trading engine) + let rows: Vec = sqlx::query_as( + "SELECT symbol, quantity, market_value \ + FROM broker_positions \ + WHERE account_id = $1 AND quantity != 0 \ + ORDER BY symbol", + ) + .bind(portfolio_id) + .fetch_all(&self.db_pool) + .await?; + + if rows.is_empty() { + warn!( + portfolio_id = %portfolio_id, + "no positions found in broker_positions table" + ); + } + + let positions = rows + .into_iter() + .map(|row| PortfolioPosition { + symbol: row.symbol, + quantity: row.quantity, + market_value: row.market_value.unwrap_or_default(), + currency: "USD".to_string(), + asset_class: "futures".to_string(), + }) + .collect(); + + Ok(positions) } async fn calculate_volatility_matrix(