Skip to main content
Skip to main content
Version: 1.0 (Current)

How to Optimize Parameters

Problem

Your strategy has multiple parameters (indicator periods, thresholds, stop loss levels) and you want to find the optimal values without overfitting to historical data.

Prerequisites

  • Completed trading algorithm with backtest results
  • Understanding of your strategy parameters
  • Sufficient historical data (minimum 1 year)
  • Basic knowledge of overfitting risks

What is Walk-Forward Optimization?

Walk-Forward Optimization (WFO) is a method that:

  1. Optimizes parameters on in-sample data (training period)
  2. Validates those parameters on out-of-sample data (testing period)
  3. Repeats this process across multiple time windows
  4. Measures consistency across all periods

This prevents overfitting by ensuring parameters work on unseen data.

Solution

Step 1: Identify Parameters to Optimize

Choose parameters that significantly impact performance:

// Example parameters to optimize
const optimizableParameters = [
{
name: 'EMA Fast Period',
path: 'entryConditions.conditions[0].indicator.period',
min: 5,
max: 20,
step: 1,
type: 'integer'
},
{
name: 'EMA Slow Period',
path: 'entryConditions.conditions[1].indicator.period',
min: 20,
max: 50,
step: 5,
type: 'integer'
},
{
name: 'RSI Period',
path: 'entryConditions.conditions[2].indicator.period',
min: 10,
max: 20,
step: 2,
type: 'integer'
},
{
name: 'RSI Oversold',
path: 'entryConditions.conditions[2].value',
min: 20,
max: 35,
step: 5,
type: 'integer'
},
{
name: 'Stop Loss %',
path: 'exitConditions.stopLoss.percentage',
min: 1.0,
max: 5.0,
step: 0.5,
type: 'float'
}
]

Best Practices:

  • Optimize 3-5 parameters maximum
  • Choose parameters with clear impact
  • Avoid optimizing too many parameters (curse of dimensionality)
  • Use reasonable ranges based on market behavior

Step 2: Configure Walk-Forward Settings

Set up the optimization configuration:

{
"walkForwardConfig": {
"enabled": true,
"inSamplePeriodDays": 90,
"outSamplePeriodDays": 30,
"windowType": "rolling",
"optimizationParameters": [
{
"name": "EMA Fast Period",
"path": "entryConditions.conditions[0].indicator.period",
"min": 5,
"max": 20,
"step": 1,
"type": "integer"
},
{
"name": "Stop Loss %",
"path": "exitConditions.stopLoss.percentage",
"min": 1.0,
"max": 5.0,
"step": 0.5,
"type": "float"
}
]
}
}

Period Guidelines:

Strategy TypeIn-SampleOut-SampleTotal Data Needed
Scalping30 days10 days6 months
Day Trading60 days20 days1 year
Swing Trading90 days30 days1.5 years
Position Trading180 days60 days2+ years

Step 3: Choose Window Type

Rolling Window (Recommended):

  • Window slides forward through time
  • Each period uses fresh data
  • More realistic simulation
Period 1: [Jan-Mar] optimize → [Apr] test
Period 2: [Feb-Apr] optimize → [May] test
Period 3: [Mar-May] optimize → [Jun] test

Anchored Window:

  • Window expands from start
  • Uses all previous data
  • More data but less realistic
Period 1: [Jan-Mar] optimize → [Apr] test
Period 2: [Jan-Apr] optimize → [May] test
Period 3: [Jan-May] optimize → [Jun] test
{
"windowType": "rolling" // or "anchored"
}

Step 4: Run Walk-Forward Optimization

Execute the optimization:

// Start walk-forward optimization
const optimization = await fetch('/api/backtest/walk-forward', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
algorithmId: "algo_123",
startDate: "2023-01-01",
endDate: "2023-12-31",
initialBalance: 100000,
symbols: ["NSE:RELIANCE"],
walkForwardConfig: {
enabled: true,
inSamplePeriodDays: 90,
outSamplePeriodDays: 30,
windowType: "rolling",
optimizationParameters: [
{
name: "EMA Fast Period",
path: "entryConditions.conditions[0].indicator.period",
min: 5,
max: 20,
step: 1,
type: "integer"
},
{
name: "Stop Loss %",
path: "exitConditions.stopLoss.percentage",
min: 1.0,
max: 5.0,
step: 0.5,
type: "float"
}
]
}
})
}).then(res => res.json())

console.log('Optimization started:', optimization.id)

Note: Walk-forward optimization takes longer than regular backtests (10-30 minutes typical).

Step 5: Interpret Walk-Forward Efficiency (WFE)

The key metric is Walk-Forward Efficiency:

Formula: WFE = Out-of-Sample Performance / In-Sample Performance

{
"walkForwardResults": [
{
"period": 1,
"inSample": {
"return": 15.2,
"profitFactor": 2.1,
"sharpeRatio": 1.8
},
"outSample": {
"return": 12.5,
"profitFactor": 1.7,
"sharpeRatio": 1.5
},
"wfe": 0.82,
"optimalParameters": {
"emaFastPeriod": 12,
"stopLossPercent": 2.5
}
}
],
"avgWFE": 0.78,
"consistentPeriods": 6,
"totalPeriods": 8,
"recommendation": "good"
}

WFE Interpretation:

  • > 1.0: Out-sample better than in-sample (rare, excellent)
  • 0.7 - 1.0: Good (acceptable degradation)
  • 0.5 - 0.7: Moderate (some overfitting)
  • < 0.5: Poor (significant overfitting)

Step 6: Analyze Consistency

Check how many periods have acceptable WFE:

function analyzeConsistency(results) {
const consistentPeriods = results.walkForwardResults.filter(
period => period.wfe >= 0.7
).length

const consistencyRate = consistentPeriods / results.totalPeriods

return {
consistentPeriods,
totalPeriods: results.totalPeriods,
consistencyRate: (consistencyRate * 100).toFixed(1) + '%',
rating: consistencyRate >= 0.75 ? 'Excellent' :
consistencyRate >= 0.60 ? 'Good' :
consistencyRate >= 0.50 ? 'Acceptable' : 'Poor'
}
}

// Example
const consistency = analyzeConsistency(wfoResults)
// { consistentPeriods: 6, totalPeriods: 8, consistencyRate: '75%', rating: 'Excellent' }

Step 7: Review Optimal Parameters

Examine the optimal parameters found in each period:

function analyzeParameterStability(results) {
const parameters = results.walkForwardResults.map(p => p.optimalParameters)

// Calculate parameter ranges
const emaFastValues = parameters.map(p => p.emaFastPeriod)
const stopLossValues = parameters.map(p => p.stopLossPercent)

return {
emaFast: {
min: Math.min(...emaFastValues),
max: Math.max(...emaFastValues),
avg: (emaFastValues.reduce((a, b) => a + b) / emaFastValues.length).toFixed(1),
stability: (Math.max(...emaFastValues) - Math.min(...emaFastValues)) &lt;= 5 ? 'Stable' : 'Unstable'
},
stopLoss: {
min: Math.min(...stopLossValues),
max: Math.max(...stopLossValues),
avg: (stopLossValues.reduce((a, b) => a + b) / stopLossValues.length).toFixed(2),
stability: (Math.max(...stopLossValues) - Math.min(...stopLossValues)) &lt;= 1.0 ? 'Stable' : 'Unstable'
}
}
}

// Stable parameters = good (not overfitted to specific values)
// Unstable parameters = bad (different optimal values each period)

Avoiding Overfitting

1. Limit Parameter Count

// ❌ Bad: Too many parameters
const tooManyParams = [
'emaFastPeriod', // 1
'emaSlowPeriod', // 2
'rsiPeriod', // 3
'rsiOversold', // 4
'rsiOverbought', // 5
'macdFast', // 6
'macdSlow', // 7
'macdSignal', // 8
'stopLoss', // 9
'takeProfit', // 10
'trailingStop' // 11
]

// ✅ Good: Focus on key parameters
const keyParams = [
'emaFastPeriod', // 1
'emaSlowPeriod', // 2
'stopLoss' // 3
]

2. Use Reasonable Ranges

// ❌ Bad: Too wide range
{
name: 'EMA Period',
min: 1,
max: 200,
step: 1 // 200 possible values!
}

// ✅ Good: Focused range
{
name: 'EMA Period',
min: 10,
max: 30,
step: 2 // 11 possible values
}

3. Require Minimum Trades

function validateOptimizationPeriod(period) {
if (period.inSample.totalTrades < 30) {
return {
valid: false,
reason: 'Insufficient in-sample trades'
}
}
if (period.outSample.totalTrades < 10) {
return {
valid: false,
reason: 'Insufficient out-sample trades'
}
}
return { valid: true }
}

4. Check for Curve Fitting

function detectCurveFitting(results) {
const warnings = []

// Warning 1: WFE too low
if (results.avgWFE < 0.6) {
warnings.push('Low WFE indicates overfitting')
}

// Warning 2: Inconsistent periods
if (results.consistentPeriods / results.totalPeriods < 0.6) {
warnings.push('Inconsistent performance across periods')
}

// Warning 3: Unstable parameters
const paramStability = analyzeParameterStability(results)
if (paramStability.emaFast.stability === 'Unstable') {
warnings.push('Parameters vary too much between periods')
}

// Warning 4: Too good in-sample
const avgInSampleReturn = results.walkForwardResults
.reduce((sum, p) => sum + p.inSample.return, 0) / results.totalPeriods
if (avgInSampleReturn > 50) {
warnings.push('In-sample returns suspiciously high')
}

return {
isCurveFitted: warnings.length >= 2,
warnings
}
}

Complete Optimization Workflow

async function optimizeStrategy() {
// 1. Start optimization
console.log('Starting walk-forward optimization...')
const optimization = await startWalkForwardOptimization({
algorithmId: "algo_123",
startDate: "2023-01-01",
endDate: "2023-12-31",
inSampleDays: 90,
outSampleDays: 30,
parameters: [
{ name: 'EMA Fast', path: '...', min: 5, max: 20, step: 1 },
{ name: 'Stop Loss', path: '...', min: 1.0, max: 5.0, step: 0.5 }
]
})

// 2. Wait for completion
const results = await waitForOptimization(optimization.id)

// 3. Analyze results
const analysis = {
wfe: results.avgWFE,
consistency: analyzeConsistency(results),
parameterStability: analyzeParameterStability(results),
curveFitting: detectCurveFitting(results)
}

// 4. Make decision
if (analysis.curveFitting.isCurveFitted) {
console.log('⚠ Strategy appears overfitted')
console.log('Warnings:', analysis.curveFitting.warnings)
return { decision: 'REJECT', reason: 'Overfitting detected' }
}

if (analysis.wfe >= 0.7 && analysis.consistency.consistencyRate >= 60) {
console.log('✓ Strategy passed optimization')
console.log('Recommended parameters:', getAverageParameters(results))
return { decision: 'ACCEPT', parameters: getAverageParameters(results) }
}

console.log('⚠ Strategy needs improvement')
return { decision: 'REVISE', suggestions: generateSuggestions(analysis) }
}

function getAverageParameters(results) {
// Use average or median of optimal parameters across periods
const allParams = results.walkForwardResults.map(p => p.optimalParameters)

return {
emaFastPeriod: Math.round(
allParams.reduce((sum, p) => sum + p.emaFastPeriod, 0) / allParams.length
),
stopLossPercent: (
allParams.reduce((sum, p) => sum + p.stopLossPercent, 0) / allParams.length
).toFixed(2)
}
}

Optimization Best Practices

  1. Use Sufficient Data: Minimum 1 year, preferably 2-3 years
  2. Test Multiple Market Conditions: Include bull, bear, and sideways markets
  3. Limit Parameters: Optimize 3-5 parameters maximum
  4. Use Reasonable Ranges: Based on market behavior, not arbitrary
  5. Require Minimum Trades: 30+ in-sample, 10+ out-sample per period
  6. Check Consistency: 60%+ periods should have WFE > 0.7
  7. Verify Parameter Stability: Optimal values shouldn't vary wildly
  8. Compare to Baseline: Optimized strategy should beat unoptimized
  9. Use Rolling Windows: More realistic than anchored
  10. Validate with Monte Carlo: Additional robustness check

Troubleshooting

Problem: Low WFE (< 0.5)

Causes:

  • Overfitting to in-sample data
  • Too many parameters
  • Unrealistic parameter ranges
  • Insufficient data

Solutions:

  • Reduce number of parameters
  • Narrow parameter ranges
  • Increase in-sample period
  • Simplify strategy logic

Problem: Inconsistent Results

Causes:

  • Market regime changes
  • Insufficient trades per period
  • Unstable parameters
  • Strategy not robust

Solutions:

  • Extend period lengths
  • Test on longer timeframe
  • Add regime filters
  • Simplify strategy

Problem: Optimization Takes Too Long

Causes:

  • Too many parameter combinations
  • Too many symbols
  • Short timeframe (1m, 5m)

Solutions:

  • Reduce parameter ranges
  • Optimize fewer parameters
  • Test single symbol first
  • Use longer timeframe