run_monte_carlo
Optimizes portfolios by running Monte Carlo simulations that test random weight combinations and evaluate risk-return characteristics.
Instructions
Run Monte Carlo simulation to find optimal portfolios.
Generates random portfolio weight combinations and evaluates their risk/return characteristics to find optimal allocations.
Note: This is computationally intensive. For large num_trials, consider using the Efficient Frontier method instead which provides mathematically optimal solutions.
Args: name: The portfolio name. num_trials: Number of random portfolios to generate (default: 5000).
Returns: Dictionary containing: - num_trials: Number of simulations run - min_volatility_portfolio: Portfolio with minimum volatility - max_sharpe_portfolio: Portfolio with maximum Sharpe ratio - simulation_stats: Statistics about the simulation - sample_portfolios: Sample of generated portfolios
Example:
result = run_monte_carlo(name="tech_stocks", num_trials=10000)
best = result['max_sharpe_portfolio']
print(f"Best Sharpe: {best['sharpe_ratio']:.2f}")
print(f"Optimal weights: {best['weights']}")
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| num_trials | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- app/tools/optimization.py:241-242 (registration)The tool is registered as an MCP tool via @mcp.tool decorator on the run_monte_carlo function inside register_optimization_tools(). The registration is triggered from app/server.py line 139.
@mcp.tool def run_monte_carlo( - app/tools/optimization.py:242-377 (handler)The core handler function for run_monte_carlo tool. It takes a portfolio name and num_trials, fetches portfolio data from the store, runs Monte Carlo optimization via MonteCarloOpt from finquant, and returns min_volatility/max_sharpe portfolios, simulation stats, and sample portfolios.
def run_monte_carlo( name: str, num_trials: int = 5000, ) -> dict[str, Any]: """Run Monte Carlo simulation to find optimal portfolios. Generates random portfolio weight combinations and evaluates their risk/return characteristics to find optimal allocations. Note: This is computationally intensive. For large num_trials, consider using the Efficient Frontier method instead which provides mathematically optimal solutions. Args: name: The portfolio name. num_trials: Number of random portfolios to generate (default: 5000). Returns: Dictionary containing: - num_trials: Number of simulations run - min_volatility_portfolio: Portfolio with minimum volatility - max_sharpe_portfolio: Portfolio with maximum Sharpe ratio - simulation_stats: Statistics about the simulation - sample_portfolios: Sample of generated portfolios Example: ``` result = run_monte_carlo(name="tech_stocks", num_trials=10000) best = result['max_sharpe_portfolio'] print(f"Best Sharpe: {best['sharpe_ratio']:.2f}") print(f"Optimal weights: {best['weights']}") ``` """ data = store.get(name) if data is None: return { "error": f"Portfolio '{name}' not found", } # Limit trials for performance if num_trials > 50000: return { "error": "num_trials exceeds maximum of 50000", "suggestion": "Use smaller num_trials or use optimize_portfolio() for exact solution", } # Rebuild portfolio prices_df = pd.DataFrame( data=data["prices"]["values"], index=pd.to_datetime(data["prices"]["index"]), columns=data["prices"]["columns"], ) allocation_df = pd.DataFrame( data=data["allocation"]["values"], columns=data["allocation"]["columns"], ) portfolio = build_portfolio(data=prices_df, pf_allocation=allocation_df) portfolio.risk_free_rate = data["settings"]["risk_free_rate"] # Calculate returns returns_df = daily_returns(prices_df).dropna() # Get initial weights initial_weights = np.array( [row[0] / 100.0 for row in data["allocation"]["values"]] ) # Run Monte Carlo optimization mc = MonteCarloOpt( returns=returns_df, num_trials=num_trials, risk_free_rate=portfolio.risk_free_rate, freq=portfolio.freq, initial_weights=initial_weights, ) opt_weights, opt_results = mc.optimisation() # Extract results symbols = prices_df.columns.tolist() min_vol_weights = opt_weights.loc["Min Volatility"].to_dict() max_sharpe_weights = opt_weights.loc["Max Sharpe Ratio"].to_dict() min_vol_results = opt_results.loc["Min Volatility"].to_dict() max_sharpe_results = opt_results.loc["Max Sharpe Ratio"].to_dict() # Get simulation statistics sim_returns = mc.df_results["Expected Return"] sim_volatility = mc.df_results["Volatility"] sim_sharpe = mc.df_results["Sharpe Ratio"] # Sample portfolios for visualization (take every nth) sample_size = min(100, num_trials) step = max(1, num_trials // sample_size) sample_portfolios = [] for i in range(0, num_trials, step): if len(sample_portfolios) >= sample_size: break sample_portfolios.append( { "expected_return": float(mc.df_results.iloc[i]["Expected Return"]), "volatility": float(mc.df_results.iloc[i]["Volatility"]), "sharpe_ratio": float(mc.df_results.iloc[i]["Sharpe Ratio"]), } ) return { "portfolio_name": name, "num_trials": num_trials, "min_volatility_portfolio": { "weights": {s: float(min_vol_weights[s]) for s in symbols}, "expected_return": float(min_vol_results["Expected Return"]), "volatility": float(min_vol_results["Volatility"]), "sharpe_ratio": float(min_vol_results["Sharpe Ratio"]), }, "max_sharpe_portfolio": { "weights": {s: float(max_sharpe_weights[s]) for s in symbols}, "expected_return": float(max_sharpe_results["Expected Return"]), "volatility": float(max_sharpe_results["Volatility"]), "sharpe_ratio": float(max_sharpe_results["Sharpe Ratio"]), }, "simulation_stats": { "return_range": [float(sim_returns.min()), float(sim_returns.max())], "volatility_range": [ float(sim_volatility.min()), float(sim_volatility.max()), ], "sharpe_range": [float(sim_sharpe.min()), float(sim_sharpe.max())], "return_mean": float(sim_returns.mean()), "volatility_mean": float(sim_volatility.mean()), "sharpe_mean": float(sim_sharpe.mean()), }, "sample_portfolios": sample_portfolios, "completed_at": datetime.now().isoformat(), } - app/tools/optimization.py:242-274 (schema)Input schema: name (str, required) and num_trials (int, default 5000, max 50000). Output structure documented in docstring and returned dict with keys: num_trials, min_volatility_portfolio, max_sharpe_portfolio, simulation_stats, sample_portfolios.
def run_monte_carlo( name: str, num_trials: int = 5000, ) -> dict[str, Any]: """Run Monte Carlo simulation to find optimal portfolios. Generates random portfolio weight combinations and evaluates their risk/return characteristics to find optimal allocations. Note: This is computationally intensive. For large num_trials, consider using the Efficient Frontier method instead which provides mathematically optimal solutions. Args: name: The portfolio name. num_trials: Number of random portfolios to generate (default: 5000). Returns: Dictionary containing: - num_trials: Number of simulations run - min_volatility_portfolio: Portfolio with minimum volatility - max_sharpe_portfolio: Portfolio with maximum Sharpe ratio - simulation_stats: Statistics about the simulation - sample_portfolios: Sample of generated portfolios Example: ``` result = run_monte_carlo(name="tech_stocks", num_trials=10000) best = result['max_sharpe_portfolio'] print(f"Best Sharpe: {best['sharpe_ratio']:.2f}") print(f"Optimal weights: {best['weights']}") ``` """ - app/tools/optimization.py:310-316 (helper)Uses MonteCarloOpt from finquant library to perform the Monte Carlo simulation. The optimisation() method returns optimal weights and results.
mc = MonteCarloOpt( returns=returns_df, num_trials=num_trials, risk_free_rate=portfolio.risk_free_rate, freq=portfolio.freq, initial_weights=initial_weights, ) - app/server.py:71-71 (registration)The tool is listed in the server's instructions/documentation as 'run_monte_carlo: Run Monte Carlo simulation for optimization'.
- run_monte_carlo: Run Monte Carlo simulation for optimization