Skip to main content
Glama

MaverickMCP

by wshobson
MIT License
165
  • Apple
optimization.py13.3 kB
"""Strategy optimization utilities for VectorBT.""" from typing import Any import numpy as np import pandas as pd class StrategyOptimizer: """Optimizer for trading strategy parameters.""" def __init__(self, engine): """Initialize optimizer with VectorBT engine. Args: engine: VectorBTEngine instance """ self.engine = engine def generate_param_grid( self, strategy_type: str, optimization_level: str = "medium" ) -> dict[str, list]: """Generate parameter grid based on strategy and optimization level. Args: strategy_type: Type of strategy optimization_level: Level of optimization (coarse, medium, fine) Returns: Parameter grid for optimization """ if strategy_type == "sma_cross": return self._sma_param_grid(optimization_level) elif strategy_type == "rsi": return self._rsi_param_grid(optimization_level) elif strategy_type == "macd": return self._macd_param_grid(optimization_level) elif strategy_type == "bollinger": return self._bollinger_param_grid(optimization_level) elif strategy_type == "momentum": return self._momentum_param_grid(optimization_level) else: raise ValueError(f"Unknown strategy type: {strategy_type}") def _sma_param_grid(self, level: str) -> dict[str, list]: """Generate SMA crossover parameter grid.""" if level == "coarse": return { "fast_period": [5, 10, 20], "slow_period": [20, 50, 100], } elif level == "fine": return { "fast_period": list(range(5, 25, 2)), "slow_period": list(range(20, 101, 5)), } else: # medium return { "fast_period": [5, 10, 15, 20], "slow_period": [20, 30, 50, 100], } def _rsi_param_grid(self, level: str) -> dict[str, list]: """Generate RSI parameter grid.""" if level == "coarse": return { "period": [7, 14, 21], "oversold": [20, 30], "overbought": [70, 80], } elif level == "fine": return { "period": list(range(7, 22, 2)), "oversold": list(range(20, 41, 5)), "overbought": list(range(60, 81, 5)), } else: # medium return { "period": [7, 14, 21], "oversold": [20, 25, 30, 35], "overbought": [65, 70, 75, 80], } def _macd_param_grid(self, level: str) -> dict[str, list]: """Generate MACD parameter grid.""" if level == "coarse": return { "fast_period": [8, 12], "slow_period": [21, 26], "signal_period": [9], } elif level == "fine": return { "fast_period": list(range(8, 15)), "slow_period": list(range(20, 31)), "signal_period": list(range(7, 12)), } else: # medium return { "fast_period": [8, 10, 12, 14], "slow_period": [21, 24, 26, 30], "signal_period": [7, 9, 11], } def _bollinger_param_grid(self, level: str) -> dict[str, list]: """Generate Bollinger Bands parameter grid.""" if level == "coarse": return { "period": [10, 20], "std_dev": [1.5, 2.0, 2.5], } elif level == "fine": return { "period": list(range(10, 31, 2)), "std_dev": np.arange(1.0, 3.1, 0.25).tolist(), } else: # medium return { "period": [10, 15, 20, 25], "std_dev": [1.5, 2.0, 2.5, 3.0], } def _momentum_param_grid(self, level: str) -> dict[str, list]: """Generate momentum parameter grid.""" if level == "coarse": return { "lookback": [10, 20, 30], "threshold": [0.03, 0.05, 0.10], } elif level == "fine": return { "lookback": list(range(10, 41, 2)), "threshold": np.arange(0.02, 0.11, 0.01).tolist(), } else: # medium return { "lookback": [10, 15, 20, 25, 30], "threshold": [0.02, 0.03, 0.05, 0.07, 0.10], } async def walk_forward_analysis( self, symbol: str, strategy_type: str, parameters: dict[str, Any], start_date: str, end_date: str, window_size: int = 252, # Trading days in a year step_size: int = 63, # Trading days in a quarter optimization_window: int = 504, # 2 years for optimization ) -> dict[str, Any]: """Perform walk-forward analysis. Args: symbol: Stock symbol strategy_type: Strategy type parameters: Initial parameters start_date: Start date end_date: End date window_size: Test window size in days step_size: Step size for rolling window optimization_window: Optimization window size Returns: Walk-forward analysis results """ results = [] # Convert dates to pandas datetime start = pd.to_datetime(start_date) end = pd.to_datetime(end_date) current = start + pd.Timedelta(days=optimization_window) while current <= end: # Optimization period opt_start = current - pd.Timedelta(days=optimization_window) opt_end = current # Test period test_start = current test_end = min(current + pd.Timedelta(days=window_size), end) # Optimize on training data param_grid = self.generate_param_grid(strategy_type, "coarse") optimization = await self.engine.optimize_parameters( symbol=symbol, strategy_type=strategy_type, param_grid=param_grid, start_date=opt_start.strftime("%Y-%m-%d"), end_date=opt_end.strftime("%Y-%m-%d"), top_n=1, ) best_params = optimization["best_parameters"] # Test on out-of-sample data if test_start < test_end: test_result = await self.engine.run_backtest( symbol=symbol, strategy_type=strategy_type, parameters=best_params, start_date=test_start.strftime("%Y-%m-%d"), end_date=test_end.strftime("%Y-%m-%d"), ) results.append( { "period": f"{test_start.strftime('%Y-%m-%d')} to {test_end.strftime('%Y-%m-%d')}", "parameters": best_params, "in_sample_sharpe": optimization["best_metric_value"], "out_sample_return": test_result["metrics"]["total_return"], "out_sample_sharpe": test_result["metrics"]["sharpe_ratio"], "out_sample_drawdown": test_result["metrics"]["max_drawdown"], } ) # Move window forward current += pd.Timedelta(days=step_size) # Calculate aggregate metrics if results: avg_return = np.mean([r["out_sample_return"] for r in results]) avg_sharpe = np.mean([r["out_sample_sharpe"] for r in results]) avg_drawdown = np.mean([r["out_sample_drawdown"] for r in results]) consistency = sum(1 for r in results if r["out_sample_return"] > 0) / len( results ) else: avg_return = avg_sharpe = avg_drawdown = consistency = 0 return { "symbol": symbol, "strategy": strategy_type, "periods_tested": len(results), "average_return": avg_return, "average_sharpe": avg_sharpe, "average_drawdown": avg_drawdown, "consistency": consistency, "walk_forward_results": results, "summary": self._generate_wf_summary(avg_return, avg_sharpe, consistency), } def _generate_wf_summary( self, avg_return: float, avg_sharpe: float, consistency: float ) -> str: """Generate walk-forward analysis summary.""" summary = f"Walk-forward analysis shows {avg_return * 100:.1f}% average return " summary += f"with Sharpe ratio of {avg_sharpe:.2f}. " summary += f"Strategy was profitable in {consistency * 100:.0f}% of periods. " if avg_sharpe >= 1.0 and consistency >= 0.7: summary += "Results indicate robust performance across different market conditions." elif avg_sharpe >= 0.5 and consistency >= 0.5: summary += "Results show moderate robustness with room for improvement." else: summary += "Results suggest the strategy may not be robust to changing market conditions." return summary async def monte_carlo_simulation( self, backtest_results: dict[str, Any], num_simulations: int = 1000, confidence_levels: list[float] | None = None, ) -> dict[str, Any]: """Run Monte Carlo simulation on backtest results. Args: backtest_results: Results from run_backtest num_simulations: Number of simulations to run confidence_levels: Confidence levels for percentiles Returns: Monte Carlo simulation results """ if confidence_levels is None: confidence_levels = [0.05, 0.25, 0.50, 0.75, 0.95] trades = backtest_results.get("trades", []) if not trades: return {"error": "No trades to simulate"} # Extract returns from trades trade_returns = [t["return"] for t in trades] # Run simulations simulated_returns = [] simulated_drawdowns = [] for _ in range(num_simulations): # Bootstrap sample with replacement sampled_returns = np.random.choice( trade_returns, size=len(trade_returns), replace=True ) # Calculate cumulative return cumulative = np.cumprod(1 + np.array(sampled_returns)) total_return = cumulative[-1] - 1 # Calculate max drawdown running_max = np.maximum.accumulate(cumulative) drawdown = (cumulative - running_max) / running_max max_drawdown = np.min(drawdown) simulated_returns.append(total_return) simulated_drawdowns.append(max_drawdown) # Calculate percentiles return_percentiles = np.percentile( simulated_returns, np.array(confidence_levels) * 100 ) drawdown_percentiles = np.percentile( simulated_drawdowns, np.array(confidence_levels) * 100 ) return { "num_simulations": num_simulations, "expected_return": np.mean(simulated_returns), "return_std": np.std(simulated_returns), "return_percentiles": dict( zip( [f"p{int(cl * 100)}" for cl in confidence_levels], return_percentiles.tolist(), strict=False, ) ), "expected_drawdown": np.mean(simulated_drawdowns), "drawdown_std": np.std(simulated_drawdowns), "drawdown_percentiles": dict( zip( [f"p{int(cl * 100)}" for cl in confidence_levels], drawdown_percentiles.tolist(), strict=False, ) ), "probability_profit": sum(1 for r in simulated_returns if r > 0) / num_simulations, "var_95": return_percentiles[0], # Value at Risk at 95% confidence "summary": self._generate_mc_summary( np.mean(simulated_returns), return_percentiles[0], sum(1 for r in simulated_returns if r > 0) / num_simulations, ), } def _generate_mc_summary( self, expected_return: float, var_95: float, prob_profit: float ) -> str: """Generate Monte Carlo simulation summary.""" summary = f"Monte Carlo simulation shows {expected_return * 100:.1f}% expected return " summary += f"with {prob_profit * 100:.1f}% probability of profit. " summary += f"95% Value at Risk is {abs(var_95) * 100:.1f}%. " if prob_profit >= 0.8 and expected_return > 0.10: summary += "Strategy shows strong probabilistic edge." elif prob_profit >= 0.6 and expected_return > 0: summary += "Strategy shows positive expectancy with moderate confidence." else: summary += "Strategy may not have sufficient edge for live trading." return summary

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wshobson/maverick-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server