TRADING TECHNIQUES

Genetic 
Algorithms
And Rule-Based Systems


by Jeffrey Owen Katz, Ph.D.,
with Donna L. McCormick

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."

Genetic algorithms solve problems in a manner similar to biological evolution - through recombination, mutation and selection.

EXERCISING GENES
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; }
                }
        }
}

FIGURE 1: C++ CODE IMPLEMENTING RULE TEMPLATES, SYSTEM TRADING STRATEGY AND FITNESS EVALUATION. The C++ function that implements this mapping strategy is shown here. Although C++ was used, there is no reason why these ideas cannot be implemented in another language or using other tools.
= = =

"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."


Jeffrey Owen Katz is a professional trader and consultant in Selden, NY. His firm, Scientific Consultant Services (516 696-3333), specializes in custom programming, provides expert consultation on systems development and the use of Omega Research's tools, and develops publicly available software for traders.
Donna L. McCormick is a writer and vice president of Scientific Consultant Services.
Excerpted from an article originally published in the February 1997 issue of Technical Analysis of STOCKS & COMMODITIES magazine. 
© Copyright 1997, Technical Analysis, Inc. All rights reserved.

Return to February Contents