Walk Forward Optimization

Walk Forward Optimization (WFO in short) is a variant of cross-validation for time series and was first published by Robert Pardo as a backtest method for algorithmic trading strategies. It trains and tests the strategy in several cycles using a data frame that "walks" over the simulation period. This allows an out-of-sample backtest that still uses most of the historical data. WFO is not restricted to backtests, but can be continued In live trading and regularly adapt the strategy parameters to the current market situation. In this way, a WFO trained strategy is essentially a 'parameter-less' system.

For greatly reducing the backtest time with WFO, Zorro separates test and training and stores all trained parameters, trading rules, or machine learning models separately for any WFO cycle. This way a backtest needs not perform all optimizations again. It automatically switches between sets of parameters or rules. To activate WFO in a strategy script, only the following variable needs to be set:

NumWFOCycles

Number of cycles in a Walk Forward Optimization / Analysis (default = 0 = no Walk Forward Optimization). If NumWFOCycles is set to a positive number at or above 2, rolling walk forward optimization is enabled with the given number of cycles; if it is set to a negative number at or below -2, anchored walk forward optimization is enabled. In Walk Forward Optimization, a data frame consisting of a training and test period is shifted over the simulation period in the given number of cycles (see image for ).

WFOCycle
Simulation period
1
LookBack
Training
Test1
2
LookBack
Training
Test2
3
LookBack
Training
Test3
4
LookBack
Training
Test4
5
 
LookBack
Training
OOS Test
LookBack
Test5
WFO Test
Look
Back
Test1
Test2
Test3
Test4
Rolling Walk Forward Optimization (NumWFOCycles = 5)
 
WFOCycle
Simulation period
1
LookBack
Training
Test1
2
LookBack
Training
Test2
3
LookBack
Training
Test3
4
LookBack
Training
Test4
5
LookBack
Training
WFO Test
Look
Back
Test1
Test2
Test3
Test4
Anchored Walk Forward Optimization (NumWFOCycles = -5)

Strategy parameters and trade rules are generated separately for every cycle in [Train] mode, and are separately loaded for every test segment in [Test] mode. This way, the strategy test is guaranteed to be out of sample - it uses only parameters and rules that were generated in the preceding training cycle from data that does not occur in the test. This simulates the behavior of real trading where parameters and rules are generated from past price data. A Walk Forward Test gives the best prediction possible of the strategy performance over time.

WFOPeriod

Alternative to NumWFOCycles; number of bars of one WFO cycle (training + test), f.i. 5*24 for a one-week WFO cycle with 1-hour bars. For getting a fixed test period of N bars, set WFOPeriod to N*100/(100-DataSplit). Since the number of WFO cycles now depends on the number of bars in the history, asset must be called before. The WFO period is automatically adjusted so that the simulation period covers an integer number of WFO cycles. 

SelectWFO

Set this to a certain WFO cycle number for selecting only this cycle in a training or test; otherwise the process runs over all cycles. Setting it to -1 selects the last cycle that's normally used for live trading.

Type:

int

WFOCycle

The number of the current WFO cycle, from 1 to NumWFOCycles. Automatically set during WFO test and training.

WFOBar

The bar number inside the current WFO cycle, starting with 0 at the begin of the cycle. Can be used to determine when the cycle starts: if(WFOBar == 0). Automatically set during a WFO test.

Type:

int, read/only

Remarks:

Examples:

// anchored WFO, 10 cycles 
function run()
{
  NumWFOCycles = -10;
  DataSplit = 80; // 20% test cycle
  DataSlope = 2;  // more weight to more recent data
  set(PARAMETERS,TESTNOW); // run a test immediately after WFO
  ...
}

// set WFO period and DataSplit from a fixed test and training cycle
function setWFO1(int TestDays,int TrainDays)
{
  WFOPeriod = (TestDays+TrainDays) * 1440/BarPeriod;
  DataSplit = 100*TrainDays/(TestDays+TrainDays);
}

// set NumWFOCycles from a fixed test cycle in days
// (DataSplit and LookBack should be set before)
function setWFO2(int TestDays)
{
asset(Asset); // requires NumBars, so asset must be set
int TestBars = TestDays * 1440/BarPeriod;
var Split = ifelse(DataSplit > 0,DataSplit/100.,0.85); int TrainBars = TestBars * Split/(1.-Split);
int TotalBars = NumBars-LookBack;
NumWFOCycles = (TotalBars-TrainBars)/TestBars + 1;
} // set a fixed date for the WFO test start // modifies StartDate, needs EndDate and TestDate in YYYYMMDD function setWFO3(int TestDate) { var Split = ifelse(DataSplit > 0,DataSplit/100.,0.85); var TrainDays; if(WFOPeriod > 0) TrainDays = Split*WFOPeriod*BarPeriod/1440; else if(NumWFOCycles > 1) TrainDays = Split/(1.-Split)*(dmy(EndDate)-dmy(TestDate))/(NumWFOCycles-1); else return; StartDate = ymd(dmy(TestDate)-TrainDays); }

See also:

Training, Tutorial, DataSplit, DataHorizon, NumSampleCycles, NumTotalCycles, NumOptCycles

 

► latest version online