Grid Search System for Strategy Parameter Optimization
You have developed a strategy based on moving averages. Periods 5/15 give one return, 7/21 another. Which to choose? Manual brute force — 50 combinations, each backtest a minute — over an hour. What if you have five parameters? 10^5 combinations — a year of work. We solve this with grid search in 10 minutes, guaranteeing the global optimum within the parameter space and saving up to 80% time.
How Grid Search Solves Parameter Selection
The system exhaustively searches all combinations of parameters within your specified ranges. For each combination, it runs a backtest and ranks results by your chosen metric — for example, Sharpe ratio or annual return. You get not just the best parameters, but a full table of all combinations for deeper analysis. This helps assess stability and avoid overfitting.
Basic Implementation and Example
import itertools from multiprocessing import Pool import pandas as pd from typing import Callable, Any def grid_search( backtest_fn: Callable[[dict], dict], param_grid: dict[str, list], n_jobs: int = -1, metric: str = 'sharpe_ratio', ) -> pd.DataFrame: param_names = list(param_grid.keys()) param_values = list(param_grid.values()) all_combinations = list(itertools.product(*param_values)) print(f"Total combinations: {len(all_combinations)}") print(f"Estimated time: ~{len(all_combinations) * 0.5:.0f} seconds") def run_single(params_tuple) -> dict: params = dict(zip(param_names, params_tuple)) try: metrics = backtest_fn(params) return {**params, **metrics} except Exception as e: return {**params, 'error': str(e), metric: float('-inf')} if n_jobs == 1: results = [run_single(combo) for combo in all_combinations] else: with Pool(processes=n_jobs if n_jobs > 0 else None) as pool: results = pool.map(run_single, all_combinations) df = pd.DataFrame(results) df = df[df.get('error').isna()] if 'error' in df.columns else df return df.sort_values(metric, ascending=False) import pandas as pd from functools import partial ohlcv = load_historical_data('BTC/USDT', '2023-01-01', '2024-01-01') def backtest_ema_crossover(params: dict) -> dict: backtester = Backtester(commission=0.001, slippage=0.0005) result = backtester.run( strategy_class=EMACrossoverStrategy, params=params, data=ohlcv, initial_cash=100_000, ) return { 'sharpe_ratio': result.metrics.sharpe_ratio, 'annual_return': result.metrics.annual_return_pct, 'max_drawdown': result.metrics.max_drawdown_pct, 'win_rate': result.metrics.win_rate, 'total_trades': result.metrics.total_trades, } param_grid = { 'fast_period': [5, 7, 9, 12], 'slow_period': [15, 21, 30, 50], 'rsi_threshold': [25, 30, 35, 40], 'stop_loss_pct': [0.02, 0.03, 0.05], } results = grid_search(backtest_ema_crossover, param_grid, n_jobs=8, metric='sharpe_ratio') print(results.head(10)[['fast_period', 'slow_period', 'rsi_threshold', 'stop_loss_pct', 'sharpe_ratio', 'annual_return']]) Analyzing Results and Visualization
Grid search results can be analyzed with heatmaps. Below is a function for visualizing the dependency of a metric on two parameters.
import matplotlib.pyplot as plt import seaborn as sns def plot_parameter_heatmap(results: pd.DataFrame, param1: str, param2: str, metric: str): pivot = results.pivot_table( values=metric, index=param1, columns=param2, aggfunc='max', ) plt.figure(figsize=(10, 8)) sns.heatmap(pivot, annot=True, fmt='.2f', cmap='RdYlGn', center=0) plt.title(f'{metric} by {param1} and {param2}') plt.tight_layout() plt.savefig(f'heatmap_{param1}_{param2}.png', dpi=150) Heatmaps help quickly identify zones of optimal values. If high Sharpe ratio appears only in a narrow region, it signals possible overfitting. We recommend examining the top 5 combinations and checking their stability on an out-of-sample set.
What's Included in the Grid Search Development
| Stage | Result |
|---|---|
| Strategy Analysis | Define parameters, ranges, and target metric. Identify hyperparameters requiring tuning. |
| Architecture Design | Design the system considering your backtester and tech stack (Python, NumPy, Foundry, etc.). |
| Implementation | Write code with parallel computing, logging, visualization. Integrate standard overfitting protections. |
| Testing | Validate on historical data, compare to baseline, run stress tests. |
| Documentation & Training | Deliver source code, setup instructions, a 2-hour team session. Provide 2 weeks of support. |
Protecting Against Overfitting
Overfitting is the main danger when optimizing parameters. We apply several techniques:
- Data splitting: 70% train for optimization, 15% validation to check top 5 combos, 15% test for final confirmation.
- Minimum trade count: Discard combinations with fewer than 30 trades — statistical insignificance.
- Stability check: Ensure that small parameter changes (e.g., fast_period from 9 to 10) do not cause sharp metric drops.
def split_data_temporal(data: pd.DataFrame, train_pct=0.7, val_pct=0.15): n = len(data) train_end = int(n * train_pct) val_end = int(n * (train_pct + val_pct)) return data[:train_end], data[train_end:val_end], data[val_end:] train, validation, test = split_data_temporal(ohlcv) results = grid_search(lambda p: backtest_fn(p, train), param_grid) # Top 5 parameters tested on validation top_params = results.head(5) for _, row in top_params.iterrows(): val_result = backtest_fn(row.to_dict(), validation) print(f"Params: {row.to_dict()}, Val Sharpe: {val_result['sharpe_ratio']:.2f}") # Final test on test set — one time, with chosen parameters We also include cross-validation for stability: split history into several periods and verify that optimal parameters work across different market conditions. This reduces the risk of unexpected performance drops in live trading.
Why Grid Search Over Other Methods?
| Method | Global Optimum Guarantee | Speed with 3–4 params | Speed with 5+ params | Implementation Simplicity |
|---|---|---|---|---|
| Grid search | Yes (within grid) | High | Low | High |
| Bayesian optimization | No | Medium | High | Medium |
| Genetic algorithm | No | Medium | High | Low |
Grid search is simpler and more reliable than Bayesian optimization when the number of parameters is up to 4. For strategies with 2–4 parameters, it's the optimal choice. For 5+ parameters, we recommend switching to Bayesian optimization. As Wikipedia states, grid search is an exhaustive method guaranteeing the best combination within the defined grid.
Process, Timeline, and Pricing
- Analytics — we study your strategy, define parameters, ranges, and optimization metric. Identify hyperparameters that need tuning.
- Design — develop the grid search architecture, optimized for your backtester and stack (Python, Foundry, etc.).
- Implementation — write code with parallel computing, logging, and visualization.
- Testing — validate on historical data, compare with baseline, apply overfitting protection.
- Deployment — deliver the system, documentation, and team training (1–2 hours).
Timeline: 3 to 10 days depending on strategy complexity and number of parameters. Pricing: calculated individually — exact cost determined after analyzing your task.
Order a custom grid search system — we'll implement it tailored to your strategy. Get a consultation: contact us to evaluate your task.







