feat(risk-data): wire get_portfolio_positions to broker_positions DB table

Replace empty Vec stub with a real sqlx::query_as query against the
broker_positions table, filtering by account_id and non-zero quantity.
Uses runtime query_as (not macro) so SQLX_OFFLINE=true works without
offline metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-01 21:56:50 +01:00
parent 09a5ebcaa7
commit 43c7aa4a4b

View File

@@ -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<Decimal>,
}
/// 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<Vec<PortfolioPosition>> {
// 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<PortfolioPositionRow> = 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(