get_portfolio_metrics
Calculate comprehensive portfolio metrics including expected return, volatility, Sharpe ratio, and value at risk to evaluate portfolio performance.
Instructions
Get comprehensive metrics for a portfolio.
Calculates and returns all key portfolio metrics including
risk-adjusted returns, volatility measures, and risk metrics.
Args:
name: The portfolio name.
Returns:
Dictionary containing:
- expected_return: Annualized expected return
- volatility: Annualized volatility (standard deviation)
- sharpe_ratio: Risk-adjusted return (Sharpe)
- sortino_ratio: Downside risk-adjusted return (Sortino)
- value_at_risk: VaR at 95% confidence
- downside_risk: Target downside deviation
- skewness: Skewness per stock
- kurtosis: Kurtosis per stock
- beta: Portfolio beta (if market index available)
- treynor_ratio: Treynor ratio (if beta available)
Example:
```
metrics = get_portfolio_metrics(name="tech_stocks")
print(f"Expected Return: {metrics['expected_return']:.2%}")
print(f"Volatility: {metrics['volatility']:.2%}")
print(f"Sharpe Ratio: {metrics['sharpe_ratio']:.2f}")
```
Caching Behavior:
Any input parameter can accept a ref_id from a previous tool call
Large results return ref_id + preview; use get_cached_result to paginate
All responses include ref_id for future reference
Preview Size: server default. Override per-call with get_cached_result(ref_id, max_size=...).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- app/tools/analysis.py:33-108 (handler)The handler function that executes the get_portfolio_metrics tool logic. It retrieves portfolio data from the store, formats skewness/kurtosis, and returns a dictionary with all portfolio metrics (expected_return, volatility, sharpe_ratio, sortino_ratio, value_at_risk, downside_risk, skewness, kurtosis, beta, treynor_ratio, settings).
@mcp.tool @cache.cached( namespace="public", ttl=None, # Deterministic - infinite TTL ) def get_portfolio_metrics(name: str) -> dict[str, Any]: """Get comprehensive metrics for a portfolio. Calculates and returns all key portfolio metrics including risk-adjusted returns, volatility measures, and risk metrics. Args: name: The portfolio name. Returns: Dictionary containing: - expected_return: Annualized expected return - volatility: Annualized volatility (standard deviation) - sharpe_ratio: Risk-adjusted return (Sharpe) - sortino_ratio: Downside risk-adjusted return (Sortino) - value_at_risk: VaR at 95% confidence - downside_risk: Target downside deviation - skewness: Skewness per stock - kurtosis: Kurtosis per stock - beta: Portfolio beta (if market index available) - treynor_ratio: Treynor ratio (if beta available) Example: ``` metrics = get_portfolio_metrics(name="tech_stocks") print(f"Expected Return: {metrics['expected_return']:.2%}") print(f"Volatility: {metrics['volatility']:.2%}") print(f"Sharpe Ratio: {metrics['sharpe_ratio']:.2f}") ``` """ data = store.get(name) if data is None: return { "error": f"Portfolio '{name}' not found", "suggestion": "Use list_portfolios() to see available portfolios", } metrics = data["metrics"] # Format skewness and kurtosis for readability skewness = {} kurtosis = {} if metrics.get("skew"): for key, value in metrics["skew"].items(): # Handle both index-based and column-based keys if isinstance(value, dict): skewness.update(value) else: skewness[str(key)] = value if metrics.get("kurtosis"): for key, value in metrics["kurtosis"].items(): if isinstance(value, dict): kurtosis.update(value) else: kurtosis[str(key)] = value return { "portfolio_name": name, "expected_return": metrics["expected_return"], "volatility": metrics["volatility"], "sharpe_ratio": metrics["sharpe"], "sortino_ratio": metrics["sortino"], "value_at_risk": metrics["var"], "downside_risk": metrics["downside_risk"], "skewness": skewness, "kurtosis": kurtosis, "beta": metrics.get("beta"), "treynor_ratio": metrics.get("treynor"), "settings": data["settings"], } - app/server.py:137-139 (registration)Registration call in server.py where register_analysis_tools is called, which internally registers get_portfolio_metrics via the @mcp.tool decorator.
register_portfolio_tools(mcp, store) register_analysis_tools(mcp, store, cache) register_optimization_tools(mcp, store, cache) - app/tools/analysis.py:22-108 (registration)Registration function that uses @mcp.tool decorator to register get_portfolio_metrics as an MCP tool with FastMCP, also applying @cache.cached with infinite TTL for deterministic caching.
def register_analysis_tools( mcp: FastMCP, store: PortfolioStore, cache: RefCache ) -> None: """Register analysis tools with the FastMCP server. Args: mcp: The FastMCP server instance. store: The portfolio store for persistence. cache: The RefCache instance for caching large results. """ @mcp.tool @cache.cached( namespace="public", ttl=None, # Deterministic - infinite TTL ) def get_portfolio_metrics(name: str) -> dict[str, Any]: """Get comprehensive metrics for a portfolio. Calculates and returns all key portfolio metrics including risk-adjusted returns, volatility measures, and risk metrics. Args: name: The portfolio name. Returns: Dictionary containing: - expected_return: Annualized expected return - volatility: Annualized volatility (standard deviation) - sharpe_ratio: Risk-adjusted return (Sharpe) - sortino_ratio: Downside risk-adjusted return (Sortino) - value_at_risk: VaR at 95% confidence - downside_risk: Target downside deviation - skewness: Skewness per stock - kurtosis: Kurtosis per stock - beta: Portfolio beta (if market index available) - treynor_ratio: Treynor ratio (if beta available) Example: ``` metrics = get_portfolio_metrics(name="tech_stocks") print(f"Expected Return: {metrics['expected_return']:.2%}") print(f"Volatility: {metrics['volatility']:.2%}") print(f"Sharpe Ratio: {metrics['sharpe_ratio']:.2f}") ``` """ data = store.get(name) if data is None: return { "error": f"Portfolio '{name}' not found", "suggestion": "Use list_portfolios() to see available portfolios", } metrics = data["metrics"] # Format skewness and kurtosis for readability skewness = {} kurtosis = {} if metrics.get("skew"): for key, value in metrics["skew"].items(): # Handle both index-based and column-based keys if isinstance(value, dict): skewness.update(value) else: skewness[str(key)] = value if metrics.get("kurtosis"): for key, value in metrics["kurtosis"].items(): if isinstance(value, dict): kurtosis.update(value) else: kurtosis[str(key)] = value return { "portfolio_name": name, "expected_return": metrics["expected_return"], "volatility": metrics["volatility"], "sharpe_ratio": metrics["sharpe"], "sortino_ratio": metrics["sortino"], "value_at_risk": metrics["var"], "downside_risk": metrics["downside_risk"], "skewness": skewness, "kurtosis": kurtosis, "beta": metrics.get("beta"), "treynor_ratio": metrics.get("treynor"), "settings": data["settings"], } - app/tools/analysis.py:38-108 (schema)Schema: takes a single string parameter 'name' (the portfolio name) and returns a dictionary with typed fields (expected_return, volatility, sharpe_ratio, sortino_ratio, value_at_risk, downside_risk, skewness, kurtosis, beta, treynor_ratio, settings).
def get_portfolio_metrics(name: str) -> dict[str, Any]: """Get comprehensive metrics for a portfolio. Calculates and returns all key portfolio metrics including risk-adjusted returns, volatility measures, and risk metrics. Args: name: The portfolio name. Returns: Dictionary containing: - expected_return: Annualized expected return - volatility: Annualized volatility (standard deviation) - sharpe_ratio: Risk-adjusted return (Sharpe) - sortino_ratio: Downside risk-adjusted return (Sortino) - value_at_risk: VaR at 95% confidence - downside_risk: Target downside deviation - skewness: Skewness per stock - kurtosis: Kurtosis per stock - beta: Portfolio beta (if market index available) - treynor_ratio: Treynor ratio (if beta available) Example: ``` metrics = get_portfolio_metrics(name="tech_stocks") print(f"Expected Return: {metrics['expected_return']:.2%}") print(f"Volatility: {metrics['volatility']:.2%}") print(f"Sharpe Ratio: {metrics['sharpe_ratio']:.2f}") ``` """ data = store.get(name) if data is None: return { "error": f"Portfolio '{name}' not found", "suggestion": "Use list_portfolios() to see available portfolios", } metrics = data["metrics"] # Format skewness and kurtosis for readability skewness = {} kurtosis = {} if metrics.get("skew"): for key, value in metrics["skew"].items(): # Handle both index-based and column-based keys if isinstance(value, dict): skewness.update(value) else: skewness[str(key)] = value if metrics.get("kurtosis"): for key, value in metrics["kurtosis"].items(): if isinstance(value, dict): kurtosis.update(value) else: kurtosis[str(key)] = value return { "portfolio_name": name, "expected_return": metrics["expected_return"], "volatility": metrics["volatility"], "sharpe_ratio": metrics["sharpe"], "sortino_ratio": metrics["sortino"], "value_at_risk": metrics["var"], "downside_risk": metrics["downside_risk"], "skewness": skewness, "kurtosis": kurtosis, "beta": metrics.get("beta"), "treynor_ratio": metrics.get("treynor"), "settings": data["settings"], } - tests/test_analysis_tools.py:95-153 (helper)Test suite for get_portfolio_metrics covering expected fields, reasonable values, and error handling for nonexistent portfolios.
class TestGetPortfolioMetrics: """Tests for get_portfolio_metrics tool.""" def test_returns_all_expected_metrics( self, analysis_tools_with_cache: tuple[dict[str, Callable[..., Any]], Any], stored_analysis_portfolio: str, ) -> None: """Test that metrics include all expected fields.""" tools, cache = analysis_tools_with_cache get_metrics = tools["get_portfolio_metrics"] raw_result = get_metrics(name=stored_analysis_portfolio) result = unwrap_cached(raw_result, cache=cache) assert "portfolio_name" in result assert result["portfolio_name"] == stored_analysis_portfolio assert "expected_return" in result assert "volatility" in result assert "sharpe_ratio" in result assert "sortino_ratio" in result assert "value_at_risk" in result assert "downside_risk" in result assert "skewness" in result assert "kurtosis" in result assert "settings" in result def test_metrics_have_reasonable_values( self, analysis_tools_with_cache: tuple[dict[str, Callable[..., Any]], Any], stored_analysis_portfolio: str, ) -> None: """Test that metrics are within reasonable bounds.""" tools, cache = analysis_tools_with_cache get_metrics = tools["get_portfolio_metrics"] raw_result = get_metrics(name=stored_analysis_portfolio) result = unwrap_cached(raw_result, cache=cache) # Expected return should be a reasonable annual return assert -1.0 <= result["expected_return"] <= 5.0 # Volatility should be positive assert result["volatility"] > 0 # Sharpe ratio should be a reasonable value assert -10.0 <= result["sharpe_ratio"] <= 10.0 def test_nonexistent_portfolio_returns_error( self, analysis_tools_with_cache: tuple[dict[str, Callable[..., Any]], RefCache], ) -> None: """Test that querying nonexistent portfolio returns error.""" tools, cache = analysis_tools_with_cache get_metrics = tools["get_portfolio_metrics"] raw_result = get_metrics(name="nonexistent_portfolio") result = unwrap_cached(raw_result, cache) assert "error" in result assert "nonexistent_portfolio" in result["error"] assert "suggestion" in result