in order to arrive at as linear equity curve as possible, I am using my own fitness function. I feed the resulting value of this fitness function to the genetic optimizer (I am using Grail GO) and GGO then tries to maximize the value.

This way, I am not interested in absolute NetProfit, but intead in a smooth, linear equity curve which lends itself to applying position sizing algorithms more easily.

I am not using the widely used least-squares linear regression calculation since that would consume too much a computing time with EasyLanguage. Instead, I am using a very similar calculation which is way faster:

1. first I calculate the average equity growth for 1 bar (total profit / number of bars) (vAvgBarEqGrowth, see below)

2. then I subtract this average value at that particular bar (for bar 100, it will be 100*average equity growth for one bar) from the actual equity value at that bar - I do this on each bar and I sum up all the differences into one temporary variable (tDiffSum)

3. this variable divided by the number of bars is the average shift of the average equity growth regression line from the ideal average growth equity line (vAvgLinShift, see below)

4. then I simply calculate the ideal growth equity line by adding the average shift (either positive or negative) to the average equity growth line (vLinEquity + vAvgLinShift, see below)

5. then I calculate the maximum deviation of the actual equity value from the ideal growth equity line by going thru all bars (vMaxDev = MaxList(vMaxDev, AbsValue(aCumEquity[tCounter] - (vLinEquity + vAvgLinShift))), see below)

6. the fitness function equals to total profit / maximum deviation (or error). GGO will try to maximize this value, thus producing very smooth equity curve (FitnessVal = aCumEquity[vTotalBars] / vMaxDev, see below).

Alternative to the total profit / max error fitness might be total profit / standard deviation. Alas, this would take up more time to calculate. The latter fitness function (TotalProfit/StdDev) would theoretically favorize solutions with well-proportioned errors from the linear equity line with only occasional outliers over jagged equity curve with a maximum outlier equal to max error. Given little difference between the two methods (I did some comparisons in excel) and faster execution of determining max error, I did chose total profit / maximum deviation (as revealed by the following code).

In the strategy code, I record the bar-by-bar equity values needed to perform the final calculation into the dynamic array aCumEquity[]:

Code: Select all

`vars:`

vTotalBars(0),

vAvgBarEqGrowth(0),

vAvgLinShift(0),

vLinEquity(0),

vMaxDev(0),

tDiffSum(0),

tCounter(0);

arrays:

aCumEquity[](0);

if BarNumber >= 1 then

begin

Array_SetMaxIndex(aCumEquity, BarNumber);

aCumEquity[BarNumber] = NetProfit + OpenPositionProfit;

vTotalBars = BarNumber;

end;

In GGO, I select any fitness function (for example, 2) and replace the code for that function by this one:

Code: Select all

`if FitnessType = 2 then`

if (winners + losers) > 0 then

begin

vAvgBarEqGrowth = aCumEquity[vTotalBars] / vTotalBars;

vLinEquity = vAvgBarEqGrowth; { linear equity at bar 1

will be equal to the average bar equity growth value}

for tCounter = 1 to vTotalBars

begin

tDiffSum = tDiffSum + (aCumEquity[tCounter] - vLinEquity);

vLinEquity = vLinEquity + vAvgBarEqGrowth;

end;

vAvgLinShift = tDiffSum / vTotalBars; { average deviation

from the perfectly linear, though shifted equity curve}

vLinEquity = vAvgBarEqGrowth;

for tCounter = 1 to vTotalBars

begin

vMaxDev = MaxList(vMaxDev,

AbsValue(aCumEquity[tCounter] - (vLinEquity + vAvgLinShift)));

vLinEquity = vLinEquity + vAvgBarEqGrowth;

end;

FitnessVal = aCumEquity[vTotalBars] / vMaxDev;

end

else

FitnessVal = -99999;

Real-life example of linear regression-fitness-optimized equity curve of one of my strategies: