This trader and consultant uses a genetic algorithm to discover the best rules and parameters for a trading system."Previously, I demonstrated that simple two-rule systems could be profitable. I used a genetic algorithm (GA) to discover the best parameters for the rules. Each rule had three parameters: two lookback periods and a threshold value. Other than the parameters, the rules remained constant. The GA was used only to determine optimal values for the lookbacks and the thresholds."
EXERCISING GENESGenetic algorithms solve problems in a manner similar to biological evolution - through recombination, mutation and selection.
In this exercise, I am going to develop a population of three-rule trading systems. Each gene will correspond to a block of four numbers; it will also correspond to a rule, via the one-to-one mapping of sets of numbers to sets of rules mentioned earlier. Each chromosome will contain three genes. A chromosome as generated by the genetic algorithm will consist of 12 numbers, the first four of which will correspond to the first gene (or rule), the next four of which will correspond to the second, and the last four of which will correspond to the third. The GA itself must be informed of gene size so it will not break up intrinsic genes when performing crossover; crossover should occur only at the boundaries of genes - that is, four-number blocks. This is achieved by setting the chunk size (a genetic optimizer component property) to four.Since each gene is composed of four numbers, I will let the first number, appropriately scaled, be an index into a table of possible rule templates (see Figure 1 below).
void Rules (float **d, int nb, int v1, float v2, float v3, float v4, float *ans) { #define LinearScale(x,a,b) ((x)*((b)-(a))/1000.0+(a)) #define BiasedPosScale(x,a) (0.000001*(x)*(x)*(a)) int lb1, lb2, per, cb; float thr, fac, tmp, tiny=1.0E-20; float *Series1=(d[71]), *AvgTrueRange=(d[72]); float *IsNewHigh=(d[73]), *IsNewLow=(d[74]); float *Commercials=(d[8]), *LargeSpecs=(d[9]); float *SmallTraders=(d[10]); for (cb=1; cb<=nb; cb++) ans[cb]=FALSE; switch (v1) { case 1: // momentum to threshold comparison lb1 = BiasedPosScale (v2, 50); lb2 = BiasedPosScale (v3, 50); fac = LinearScale (v4, -2.5, 2.5); for (cb=1; cb<=nb; cb++) Series1[cb]=TrueRange(d,cb); AverageS (AvgTrueRange, Series1, 100, nb); for (cb=2+max(lb1,max(lb2,100)); cb<=nb; cb++) { thr = fac * AvgTrueRange[cb] * sqrt (abs (lb1 - lb2)); if (C[cb-lb1] - C[cb-lb2] > thr) ans[cb] = TRUE; } break; case 2: // price to simple moving average comparison per = 2 + BiasedPosScale (v2, 48); AverageS (Series1, C, per, nb); for (cb=1+per; cb<=nb; cb++) ans[cb] = Compare (C[cb], Series1[cb], v4-500); break; case 3: // COT commercials index threshold comparison thr = LinearScale (v2, 0, 100); lb1 = 12 + BiasedPosScale (v3, 50); for (cb=1+lb1; cb<=nb; cb++) ans[cb] = Compare (Commercials[cb-lb1], thr, v4-500); break; case 4: // COT large speculators index threshold comparison thr = LinearScale (v2, 0, 100); lb1 = 12 + BiasedPosScale (v3, 50); for (cb=1+lb1; cb<=nb; cb++) ans[cb] = Compare (LargeSpecs[cb-lb1], thr, v4-500); break; case 5: // COT small traders index threshold comparison thr = LinearScale (v2, 0, 100); lb1 = 12 + BiasedPosScale (v3, 50); for (cb=1+lb1; cb<=nb; cb++) ans[cb] = Compare (SmallTraders[cb-lb1], thr, v4-500.0); break; case 6: // price to exponential moving average comparison per = 2 + BiasedPosScale (v2, 50); XAverageS (Series1, C, per, nb); for (cb=2*per; cb<=nb; cb++) ans[cb] = Compare (C[cb], Series1[cb], v4-500.0); break; case 7: // open interest decline threshold comparison lb1 = 2 + BiasedPosScale (v2, 50); thr = LinearScale (v3, 0.01, 0.50); for (cb=1+lb1; cb<=nb; cb++) { tmp = (OI[cb-lb1] - OI[cb-1]) / (OI[cb-lb1] + tiny); if (tmp > thr) ans[cb] = TRUE; } break; case 8: // recent new highs lb1 = 1 + BiasedPosScale (v2, 50); lb2 = 1 + BiasedPosScale (v3, 8); for (cb=lb1+2; cb<=nb; cb++) IsNewHigh[cb] = (H[cb] > Highest(H,lb1,cb-1)) ? 1 : 0; for (cb=lb1+lb2+4; cb<=nb; cb++) if (Highest (IsNewHigh, lb2, cb) > 0.5) ans[cb]=TRUE; break; case 9: // recent new lows lb1 = 1 + BiasedPosScale (v2, 50); lb2 = 1 + BiasedPosScale (v3, 8); for (cb=lb1+2; cb<=nb; cb++) IsNewLow[cb] = (L[cb] < Lowest(L,lb1,cb-1)) ? 1 : 0; for (cb=lb1+lb2+4; cb<=nb; cb++) if (Highest (IsNewLow, lb2, cb) > 0.5) ans[cb]=TRUE; break; case 10: // open interest ascent threshold comparison lb1 = 2 + BiasedPosScale (v2, 50); thr = LinearScale (v3, 0.01, 0.99); for (cb=1+lb1; cb<=nb; cb++) { tmp = (OI[cb-1] - OI[cb-lb1]) / (OI[cb-lb1] + tiny); if (tmp > thr) ans[cb] = TRUE; } break; default: FatalError ("Attemp to select undefined rule template"); break; } #undef BiasedPosScale #undef LinearScale } void Model_Art4 (float **d, int nb, float *p, float *res, TRDSIM &tsim) { int i, j, k, cb; float stop_loss, profit_target, ppt; float *rule1=(d[101]), *rule2=(d[102]), *rule3=(d[103]); float *MyTrueRange=(d[104]), *MyAvgTrueRange=(d[105]); // evaluate rules for all bars Rules (d, nb, p[1], p[2], p[3], p[4], rule1); Rules (d, nb, p[5], p[6], p[7], p[8], rule2); Rules (d, nb, p[9], p[10], p[11], p[12], rule3); // calculate average true range for all bars for(cb=1; cb<=nb; cb++) MyTrueRange[cb]=TrueRange(d,cb); AverageS (MyAvgTrueRange, MyTrueRange, 100, nb); // loop over bars to simulate trading for (cb = 180; cb <= nb; cb++) { // simulated account update tsim.update (O[cb], H[cb], L[cb], C[cb], cb); // entry rules if (tsim.position() == 0) { if (rule1[cb]==TRUE && rule2[cb]==TRUE && rule3[cb]==TRUE) tsim.buy_open(); } // exit rules if (tsim.position() > 0) { if (tsim.bars_since_entry() > 5) tsim.exit_long_open(); profit_target = tsim.entry_price() + 4 * MyAvgTrueRange[cb]; stop_loss = tsim.entry_price() - 7500 * DOLLARS; tsim.exit_long_limit (profit_target); tsim.exit_long_stop (stop_loss); } // fitness evaluation leaving 800 bars out-of-sample if (cb == nb - 800) { if (tsim.trades_long() > 0) { res[1] = tsim.net_long() - 2.0 * tsim.max_drawdown(); ppt = tsim.net_long() / tsim.trades_long(); if (tsim.trades_long()>80 && ppt>700) res[1]+=20000; if (tsim.trades_long()>140 && ppt>1000) res[1]+=20000; if (tsim.trades_long()>180 && ppt>1200) res[1]+=20000; } else { res[1] = -9999999; } } } }
= = = "We have hit upon a methodology that provides a good, solid basis for successful system development. The use of a GA to select and instantiate rule templates works well. In the previous attempt, the GA was allowed to discover the best parameters for our rules, and it did so quite satisfactorily. This time, we also let it select the rule combinations that make up the system, and the genetic algorithm produced truly excellent results. The approach is certainly one that can serve as the basis for further system development incorporating other technologies (such as neural networks) as well as other kinds of knowledge that may be acquired about the markets."