Parameter Optimization with Grid Search¶
Optimize strategy parameters using exhaustive grid search.
Quick Start¶
#include "flox/backtest/backtest_optimizer.h"
#include "flox/backtest/optimization_stats.h"
using namespace flox;
// 1. Define your parameters
struct MAParams {
int fastPeriod;
int slowPeriod;
std::string toString() const {
return "fast=" + std::to_string(fastPeriod) +
",slow=" + std::to_string(slowPeriod);
}
};
// 2. Define parameter grid
struct MAGrid {
std::vector<int> fastPeriods = {5, 10, 15, 20};
std::vector<int> slowPeriods = {20, 30, 40, 50};
size_t totalCombinations() const {
return fastPeriods.size() * slowPeriods.size();
}
MAParams operator[](size_t index) const {
return {
fastPeriods[index / slowPeriods.size()],
slowPeriods[index % slowPeriods.size()]
};
}
};
// 3. Create optimizer
BacktestOptimizer<MAParams, MAGrid> optimizer;
optimizer.setParameterGrid(MAGrid{});
// 4. Set backtest factory
optimizer.setBacktestFactory([&](const MAParams& params) {
// Create and run your strategy with these params
return runBacktest(params);
});
// 5. Run optimization
auto results = optimizer.runLocal();
// 6. Analyze results
auto ranked = BacktestOptimizer<MAParams, MAGrid>::rankResults(
results, RankMetric::SharpeRatio);
std::cout << "Best params: " << ranked[0].parameters.toString() << "\n";
std::cout << "Sharpe: " << ranked[0].sharpeRatio() << "\n";
Components¶
BacktestOptimizer¶
template <typename ParamsT, typename GridT>
class BacktestOptimizer {
void setParameterGrid(const GridT& grid);
void setBacktestFactory(BacktestFactory factory);
void setProgressCallback(ProgressCallback callback);
size_t totalCombinations() const;
std::vector<OptimizationResult<ParamsT>> runLocal(size_t numThreads = 0);
OptimizationResult<ParamsT> runSingle(size_t index);
static std::vector<OptimizationResult<ParamsT>> rankResults(
std::vector<OptimizationResult<ParamsT>> results,
RankMetric metric,
bool ascending = false);
static std::vector<OptimizationResult<ParamsT>> filterResults(
const std::vector<OptimizationResult<ParamsT>>& results,
Predicate&& predicate);
static bool exportToCSV(
const std::vector<OptimizationResult<ParamsT>>& results,
const std::filesystem::path& path);
};
OptimizationResult¶
template <typename ParamsT>
struct OptimizationResult {
ParamsT parameters;
BacktestStats stats;
double sharpeRatio() const;
double sortinoRatio() const;
double calmarRatio() const;
double totalReturn() const;
double maxDrawdown() const;
double winRate() const;
double profitFactor() const;
size_t totalTrades() const;
};
RankMetric¶
enum class RankMetric {
SharpeRatio,
SortinoRatio,
CalmarRatio,
TotalReturn,
MaxDrawdown,
WinRate,
ProfitFactor
};
Grid Requirements¶
Your grid type must provide:
struct MyGrid {
// Return total number of parameter combinations
size_t totalCombinations() const;
// Return parameters at index [0, totalCombinations())
MyParams operator[](size_t index) const;
};
Your params type must provide:
Progress Tracking¶
optimizer.setProgressCallback(
[](size_t completed, size_t total, const OptimizationResult<MAParams>& latest) {
std::cout << "\rProgress: " << completed << "/" << total
<< " (Sharpe: " << latest.sharpeRatio() << ")" << std::flush;
});
Filtering Results¶
// Only profitable strategies with sharpe > 1.0
auto filtered = BacktestOptimizer<MAParams, MAGrid>::filterResults(
results,
[](const auto& r) {
return r.totalReturn() > 0 && r.sharpeRatio() > 1.0;
});
Statistical Analysis¶
using Stats = OptimizationStatistics<MAParams, MAGrid>;
// Summary with mean/stddev
Stats::printSummary(results);
// Generate markdown report
Stats::generateReport(results, "report.md");
// Extract metric values
auto sharpes = extractMetric(results, RankMetric::SharpeRatio);
double mean = detail::mean(sharpes);
double stddev = detail::stddev(sharpes);
// Bootstrap confidence interval
auto ci = Stats::bootstrapCI(sharpes, 0.95, 10000);
// ci.lower, ci.median, ci.upper
// Correlation between metrics
auto returns = extractMetric(results, RankMetric::TotalReturn);
double corr = Stats::correlation(sharpes, returns);
// Permutation test for significance
auto group1 = /* top 10 sharpes */;
auto group2 = /* bottom 10 sharpes */;
double pValue = Stats::permutationTest(group1, group2, 10000);
Export Results¶
// CSV export
BacktestOptimizer<MAParams, MAGrid>::exportToCSV(ranked, "results.csv");
// Markdown report
Stats::generateReport(results, "optimization_report.md");
Best Practices¶
- Avoid overfitting - More parameters = higher overfitting risk
- Use walk-forward analysis - Optimize on train, validate on test
- Check parameter stability - Best params should have good neighbors
- Consider transaction costs - Include realistic slippage/fees
- Statistical significance - Use permutation tests to validate
Example: Full Optimization Pipeline¶
// 1. Load data
auto reader = replay::createMultiSegmentReader(dataDir, filter);
// 2. Define grid
MAGrid grid;
grid.fastPeriods = {5, 10, 15, 20, 25};
grid.slowPeriods = {20, 30, 40, 50, 60};
// 3. Setup optimizer
BacktestOptimizer<MAParams, MAGrid> optimizer;
optimizer.setParameterGrid(grid);
optimizer.setBacktestFactory([&](const MAParams& p) {
return runStrategyBacktest(p, reader);
});
// 4. Run
auto results = optimizer.runLocal();
// 5. Rank and filter
auto ranked = BacktestOptimizer<MAParams, MAGrid>::rankResults(
results, RankMetric::SharpeRatio);
auto stable = BacktestOptimizer<MAParams, MAGrid>::filterResults(
ranked, [](const auto& r) { return r.totalTrades() > 30; });
// 6. Analyze top performer
const auto& best = stable[0];
std::cout << "Best: " << best.parameters.toString() << "\n";
std::cout << "Sharpe: " << best.sharpeRatio() << "\n";
std::cout << "Return: " << best.totalReturn() << "\n";
std::cout << "Trades: " << best.totalTrades() << "\n";
// 7. Export
BacktestOptimizer<MAParams, MAGrid>::exportToCSV(stable, "results.csv");