SnowCron

.COM


SnowCron.com








Learn Touch Typing

Using Self-Organizing Map to create Neural Network Trading System. FOREX Technical Analysis with Auto Classification Kohonen Neural Network.

This example uses the Cortex Neural Networks Software built-in scripting language, so please read the Scripting language guide first.




Neural Network Trading with Kohonen Neural Network.

In this free online tutorial you will find the "full cycle" of using Cortex built-in Self Organizing Maps (SOM, kohonen neural networks) for Forex trading (or stock trading, the idea is the same).

You will learn how to choose inputs for the Self Organizing Maps, and how to decide what to do with the output.

You will find an example of a ready to use script that allows to perform optimization of both the Self Organizing Maps (number of neurons) and the trading parameters (stop loss etc.)

Finally (the part that is not present in most tutorials), you will learn what to do next. After all, Cortex Neural Networks Software cannot do real time trading, you need to use something like Trade Station, MetaStocks or MetaTrader. How to port the Self Organizing Maps based forex trading strategy from Cortex to your favorite trading platform? Do you have to deal with DLLs, ActiveX controls and low-level programming? The answer is NO.
Cortex comes with the easy to use feature that allows you to easily port the resulting (trained) Self Organizing Maps to the scripting language of your trading platform. No DLLs, DDE, ActiveX or any other low-level solutions - everything is plain and simple.

Important note: this is NOT a "how to trade" tutorial. Instead, it tells you how to use Cortex, but you still need to invent your own trading system. The one we use here is barely a starting point, and shouldn't be used for Forex trading "as is". The idea of this text is to teach you to create Self Organizing Maps-based forex trading strategy and to port them to the trading platform of your choice. The example is, however, ovesimplified, and can only be used as the illustration of trading principles. Same way, the MACD trading system, that can be found in many tutorials, is not working well anymore (as markets have changed), but still is a good example of using indicators for mechanical trading.
In two words: do your own analysis.

Another important note: the tutorial is using examples, lots of them. To make your life easier, I have included them all, not just fragments. However it makes the text much longer. Also, I am going from the very first, clumsy, trading systems, to more advanced, every time explaining what had been improved and why. Be patient, or jump directly to the section you need.

Final important note: the code is not something carved in stone, it could change while this text was written. The final versions of script files are included in Cortex Neural Networks Software archive.

Clustering Data: Simple test

First of all, let's try a straightforward approach - let's feed our Self Organizing Map with sequence of 0 and 1. This should give us two clusters, that is easy to distinguish visually:

som_01.tsc

void main()
{
    // Clear the output window
    OUT_CLEANUP();

    // Place to store temporary images (charts)
    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    // ---

    double hFile;
    double i;

    // We do not use lags here, so it is a single element with value 0 
    array arrLags = CREATE_ARRAY(0);

    // Columns to load from lag file. 0th column has record number,
    // so we need 1th column    
    array arrColumnNumbers = CREATE_ARRAY(0);
    for(double nInput = 0; nInput < 1; nInput = nInput + 1)
    {
        arrLags[nInput] = nInput;
        arrColumnNumbers[nInput] = nInput + 1;
    }

    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\som_01.lgg";
    CreateLagFile(GET_FULL_PATH(strLagFileName), arrLags, nRemoveFirst);

    double nIterations = 500; 
    double nNumOfInputs = 1 * ARRAY_SIZE(arrLags);
    double nDimX = 50;
    double nDimY = 50;
    double nRedrawEvery = 50;
    string strSomFileName = "..\\CortexPro\\data\\som\\nn\\som_01.kh";

    // To save time, uncomment for first use only
//    CREATE_SOM(strSomFileName, bIsPathRelative, nNumOfInputs, 
//        nIterations, nDimX, nDimY, nRedrawEvery);
//    double bStartLearning = 0;
//    OPEN_SOM_DLG(strSomFileName, bIsPathRelative, strLagFileName, 
//        bIsPathRelative, "", 1, "", arrColumnNumbers, bStartLearning);

    double hSom = OPEN_SOM("..\\CortexPro\\data\\som\\nn\\som_01.kh", 
        bIsPathRelative);
    array arrWinners = APPLY_SOM(hSom, strLagFileName, bIsPathRelative, 
        "", 1, "", arrColumnNumbers);

    Chart("eurusd_h1");
        
    CLOSE_SOM(hSom);
    
    PRINT("%s\r\n", "Done");
}

// ------

// Here, we simply use i%2 to create sequence of 0-1-0-1...
void CreateLagFile(string strLagName, array arrLags, double nRemoveFirst)
{
    PRINT("%s\r\n", "Creating lag file");
    
    array arrInput = CREATE_ARRAY(0);
    array arrX = CREATE_ARRAY(0);

    hFile = F_OPEN(strLagName, "wb");

    F_PRINT(hFile, "%s", "No,Noc");

    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        F_PRINT(hFile, ",Noc-%.0f", arrLags[i]); 
    }
    F_PRINT(hFile, "%s", "\r\n");

    for(i = 0; i < 40; i = i + 1)
    {
        F_PRINT(hFile, "%.0f", i);
        F_PRINT(hFile, ",%f", i % 2);     
        arrX[i] = i;
        arrInput[i] = i % 2;
    
        F_PRINT(hFile, "%s", "\r\n");
    }
    F_CLOSE(hFile);
}

// Finally, we draw two charts, to see how our data crart corresponds 
// to our SOM clasters
void Chart(string strForexName)
{
    string strXML = "<forex>\r\n";

    strXML = strXML + 
        "\t<symbol>\r\n\t\t<symbol>\r\n\t\t\tClose\r\n";
    strXML = strXML + "\t\t</symbol>\r\n";
    strXML = strXML + "\t\t" + SAVE_CHART(400, 150, 0, 
        strImagePath + strForexName + ".png", arrX, arrInput);
    strXML = strXML + "\t</symbol>\r\n";

    strXML = strXML + 
        "\t<symbol>\r\n\t\t<symbol>\r\n\t\t\tNoc, ";
    strXML = strXML + "Interval = " + NUM2STR(nInterval, "%.0f\r\n");
    strXML = strXML + "\t\t</symbol>\r\n";
    strXML = strXML + "\t\t" + SAVE_CHART(400, 300, 0, 
        strImagePath + 
        strForexName + "_noc.png", arrX, arrWinners);
    strXML = strXML + "\t</symbol>\r\n";

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", "chart_forex_som", 
        "root", strXML);
    SHOW_XML(strImagePath + "chart_forex_som.xml");
}


As we can see, Self Organizing Maps can easily handle this task, creating two clasters:

When we plot data and winner neurons, we can see, that the system works well - "0" and "1" are clearly separated.

Clustering Map: Visualizing results

The approach we used in the previous example is quite clumsy - we plotted numbers of winner neurons. This is not very informative, and if we want to use this information, for example, as an input of neural networks software - not very useful. The reason is - the number of a neuron is within a 0 - size of a matrix range, and Neural Network will have to figure out a complex relation between it and a cluster neuron belongs to. Also, on a chart, it will give us a non-obvious line.

In a SOM, we can uniquely identify a neuron by its coordinates (X, Y) and a signal it produces - by adding a Z - coordinate. Also, a color in a computer graphics is usually represented by (Red, Green, Blue) vector, so here is a trick: let's ask our system to produce not the neuron numbers, but the corresponding "colors".

We use the following formulas in C++ language:

    red   = (int)((double)pNode->m_nCol / pNode->m_nCellsAcross * 128);
    green = (int)((double)pNode->m_nRow / pNode->m_nCellsUp * 128);
    blue  = (int)(255.0 * pNode->m_dWin / pNode->m_dMaxWin);

Let's walk through the code, this time, we are going to use a SIN(X) function.

som_02.tsc

void main()
{
    OUT_CLEANUP();
    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    // ---

    double hFile;
    double i;

    double bIsPathRelative = 1;

    // We create 10 lags, so that the SOM gets the following pattern:
    // SIN(x[n]), SIN(x[n-1]), SIN([n-2]),... SIN([n-9])

    // arrColumnNumbers are numbers of columns to use, in this example
    // they are 1, 2, ... 10. 0 is used by the row number, and 
    // we have to skip it

    array arrColumnNumbers = CREATE_ARRAY(0);
    for(double nInput = 0; nInput < 10; nInput = nInput + 1)
    {
        arrLags[nInput] = nInput;
        arrColumnNumbers[nInput] = nInput + 1;
    }

    // Max. lag
    // We cannot use first 10 records, as we cannot create SIN([n-10])
    // for record number less then 10.
    double nRemoveFirst = arrLags[ARRAY_SIZE(arrLags) - 1];

    // Create file with lags
    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\som_02.lgg";
    CreateLagFile(GET_FULL_PATH(strLagFileName), arrLags, nRemoveFirst);

    // Default values for SOM dialog

    double nIterations = 500; 
    double nNumOfInputs = 1 * ARRAY_SIZE(arrLags);
    double nDimX = 50;
    double nDimY = 50;
    double nRedrawEvery = 50;
    string strSomFileName = "..\\CortexPro\\data\\som\\nn\\som_02.kh";

    // Create a file with untrained SOM
    CREATE_SOM(strSomFileName, bIsPathRelative, nNumOfInputs, 
        nIterations, nDimX, nDimY, nRedrawEvery);

    // Bring up the SOM dialog
    double bStartLearning = 0;
    OPEN_SOM_DLG(strSomFileName, bIsPathRelative, strLagFileName, 
        bIsPathRelative, "", 1, "", arrColumnNumbers, bStartLearning);

    // Now we need to get a handle of the resulting SOM
    double hSom = OPEN_SOM(strSomFileName , bIsPathRelative);

    // We have two modes: 0 returns winning neuons numbers, 1 returns colors.
    // Note, that for N input patterns (records in lags file) we get array of 
    // colors with the size equal 3 * N, it contains triplets (RGB) for each
    // input record.
    
    double bMode = 1;
    array arrColors = APPLY_SOM(hSom, strLagFileName, bMode, bIsPathRelative, 
        "", 1, "", arrColumnNumbers);

    // Draw chart and close the SOM

    Chart("eurusd_h1");
    CLOSE_SOM(hSom);
}

// ------

void CreateLagFile(string strLagName, array arrLags, double nRemoveFirst)
{
    // arrX contains record numbers to plot on X axe,
    // arrInput is SIN(x)

    array arrInput = CREATE_ARRAY(0);
    array arrX = CREATE_ARRAY(0);

    hFile = F_OPEN(strLagName, "wb");

    // Header, in APPLY_SOM we provide "1" to skip it.
    F_PRINT(hFile, "%s", "X, Y");
    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        F_PRINT(hFile, ",Y-%.0f", arrLags[i]); 
    }
    F_PRINT(hFile, "%s", "\r\n");

    // As SIN(x) is within (-1, 1) range, and SOM expects (0, 1), 
    // we need to squeeze it to fit the expected range.
    // So instead of using SIN(x), we use (SIN(x) - (-1)) / 2

    double dMinQuote = -1;
    double dMaxQuote = 1;
    for(i = nRemoveFirst; i < 360 * 3; i = i + 1)
    {
        F_PRINT(hFile, "%.0f", i - nRemoveFirst + 1);
        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT    (hFile, ",%f", 
                (SIN(i - arrLags[j]) - dMinQuote) / (dMaxQuote - dMinQuote));     
        }
        arrInput[i - nRemoveFirst] = SIN(i);
        arrX[i - nRemoveFirst] = i;

        F_PRINT(hFile, "%s", "\r\n");
    }

    F_CLOSE(hFile);
}

void Chart(string strForexName)
{
    string strXML = "<forex>\r\n";

    strXML = strXML + 
        "\t<symbol>\r\n\t\t<symbol>\r\n\t\t\tClose\r\n";
    strXML = strXML + "\t\t</symbol>\r\n";
    strXML = strXML + "\t\t" + SAVE_CHART_SMOOTH(arrColors, 700, 500, 0, 
        strImagePath + strForexName + ".png", arrX, arrInput);
    strXML = strXML + "\t</symbol>\r\n";

    strXML = strXML + 
        "\t<symbol>\r\n\t\t<symbol>\r\n\t\t\t";//Noc, ";
    strXML = strXML + "\t\t</symbol>\r\n";

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", "chart_forex_som", 
        "root", strXML);
    SHOW_XML(strImagePath + "chart_forex_som.xml");
}

As you can see, classifying patterns in SIN(x) is done fine. Also, on a "smooth colors" chart, it is easy to see, that colors are properly assigned to similar "trend" parts of a chart.

Forex Signals: Using real quotes

Now, let's use real quotes and see if our self-organizing map will be able to handle them same way it handled SIN(x).

void main()
{
    OUT_CLEANUP();
    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    // ---

    double hFile;
    double i;

    // ***** Loading data
    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\eurusd_h1.txt";
    double bIsPathRelative = 1;

    array     arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    PRINT("%s\r\n", "Loading data");

    TABLE_LOADER(strDataFileName, bIsPathRelative, 0, "", 0, "", 0, 
        arrDate, 1, arrTime, 2, arrOpen, 3, arrHigh, 4, arrLow, 5, arrClose);

    array arrColumnNumbers = CREATE_ARRAY(0);
    for(double nInput = 0; nInput < 48; nInput = nInput + 1)
    {
        // We use larger lags here
        arrLags[nInput] = nInput * nInput;
        arrColumnNumbers[nInput] = nInput;
    }

    // Max. lag
    double nRemoveFirst = arrLags[ARRAY_SIZE(arrLags) - 1];

    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\som_03.lgg";
    CreateLagFile(GET_FULL_PATH(strLagFileName), arrLags, nRemoveFirst);

    double nIterations = 1000; 
    double nNumOfInputs = 1 * ARRAY_SIZE(arrLags);
    double nDimX = 50;
    double nDimY = 50;
    double nRedrawEvery = 50;
    string strSomFileName = "..\\CortexPro\\data\\som\\nn\\som_03.kh";

    CREATE_SOM(strSomFileName, bIsPathRelative, nNumOfInputs, 
        nIterations, nDimX, nDimY, nRedrawEvery);

    PRINT("%s\r\n", "Opening SOM dialog, teaching the SOM");
    
    double bStartLearning = 0;
    OPEN_SOM_DLG(strSomFileName, bIsPathRelative, strLagFileName, 
        bIsPathRelative, "", 1, "", arrColumnNumbers, bStartLearning);

    PRINT("%s\r\n", "Creating chart");

    double hSom = OPEN_SOM(strSomFileName , bIsPathRelative);
    double bMode = 1;
    array arrColors = APPLY_SOM(hSom, strLagFileName, bMode, 
        bIsPathRelative, "", 1, "", arrColumnNumbers);

    Chart("eurusd_h1");
        
    CLOSE_SOM(hSom);
    
    PRINT("%s\r\n", "Done");
}

// ------
void CreateLagFile(string strLagName, array arrLags, double nRemoveFirst)
{
    PRINT("%s\r\n", "Creating lag file");
    
    hFile = F_OPEN(strLagName, "wb");

    F_PRINT(hFile, "%s", "X, Y");

    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        F_PRINT(hFile, ",Y-%.0f", arrLags[i]); 
    }
    F_PRINT(hFile, "%s", "\r\n");

    double dMinQuote = ARRAY_MIN(arrClose, -1, -1);
    double dMaxQuote = ARRAY_MAX(arrClose, -1, -1);
    
    array arrX = CREATE_ARRAY(0);
    array arrY = CREATE_ARRAY(0);
    
    for(i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        F_PRINT(hFile, "%.0f", i - nRemoveFirst + 1);
        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT    (hFile, ",%f", 
                (arrClose[i - arrLags[j]] - dMinQuote) 
                    / (dMaxQuote - dMinQuote));      
        }

        arrX[i - nRemoveFirst] = arrDate[i];
        arrY[i - nRemoveFirst] = arrClose[i];
        F_PRINT(hFile, "%s", "\r\n");
    }

    F_CLOSE(hFile);
}

void Chart(string strForexName)
{
    string strXML = "<forex>\r\n";

    strXML = strXML + 
        "\t<symbol>\r\n\t\t<symbol>\r\n\t\t\tClose\r\n";
    strXML = strXML + "\t\t</symbol>\r\n";
    strXML = strXML + "\t\t" + SAVE_CHART_SMOOTH(arrColors, 3000, 800, 1, 
        strImagePath + strForexName + ".png", arrDate, arrClose);
    strXML = strXML + "\t</symbol>\r\n";

    strXML = strXML + 
        "\t<symbol>\r\n\t\t<symbol>\r\n\t\t\t";
    strXML = strXML + "\t\t</symbol>\r\n";
    strXML = strXML + "\t</symbol>\r\n";

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", "chart_forex_som", 
        "root", strXML);
    SHOW_XML(strImagePath + "chart_forex_som.xml");
}

As you can see from the chart, the SOM worked, but... The classification is not very useful. The problem is, as one would expect, in the fact, that the chart (CLOSE quotes for EURUSD) is going up, so the same pattern at the beginning of a chart and at its end is considered (by self-organizing map) as two completely different patterns.

Forex Trading Strategy: Using NOC indicator

NOC (Normalize On Condition) indicator was created and used in one of the previous articles to handle the requirement of the NN - it need normalized data to work. We are going to use NOC with self-organizing map to classify trends of the CLOSE quotes.

Note, that I personally DO NOT think that NOC is a perfect indicator to be used with SOM. Some kind of a claster indicator, showing "money flow" between different currencies, will most likely do a much better job. So please, consider this as an example, and build your own trading system.

To "crash test" our system, you can use eurusd_h1_long.txt quotes file, that is included in the Cortex archive, together with the shorter eurusd_h1.txt. When you use it (by replacing the short one in the code), you can see our system's behaviour in the out of sample test, on the data, it never seen. In that test, NOC does not fail, but does not shine either.

Also, we use cycles to find "nicer" parameters.

som_04.tsc

void main()
{
    OUT_CLEANUP();
    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    // Delete old images
    PRINT("%s\r\n", "Deleting image files...");

    array_s arrDirList = GET_DIR(strImagePath, 0, "*.png");
    for(double n = 0; n < ARRAY_SIZE(arrDirList); n = n + 1)
    {
        F_UNLINK(arrDirList[n]);
    }

    // ---

    double hFile;
    double i;

    // ***** Loading data
    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\eurusd_h1.txt";
    double bIsPathRelative = 1;

    array arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    PRINT("%s\r\n", "Loading data");

    TABLE_LOADER(strDataFileName, bIsPathRelative, 0, "", 0, "", 0, 
        arrDate, 1, arrTime, 2, arrOpen, 3, arrHigh, 4, arrLow, 5, arrClose);
    arrTime = arrTime / (24 * 60);
    arrDate = arrDate + arrTime;

    // We need to decide, what NOC parameters to use: 
    // Interval, Min. range and MA to apply to a result
    array_s arrParameters = CREATE_ARRAY_S(0);
    arrParameters[0] = "128,0.012,2";
//    arrParameters[1] = "128,0.012,4";
//    arrParameters[2] = "128,0.012,8";
//    arrParameters[3] = "128,0.012,16";
//    arrParameters[4] = "128,0.012,32";
//    arrParameters[5] = "128,0.012,64";
//    arrParameters[6] = "128,0.012,128";
//    arrParameters[7] = "128,0.012,256";

    array arrLags = CREATE_ARRAY(0);
    array_s arrStrLags = CREATE_ARRAY_S(0);

    // Choose lags to use, first number is num. of lags
    arrStrLags[0] = "17,1,2,3,4,6,8,10,12,14,16,20,24,30,34,38,46,54";

    // Columns to load from lag file, we will fill it automatically
    array arrColumnNumbers = CREATE_ARRAY(0);
    
    double nRemoveFirst = 2000;
    
    string strLagFileName = "..\\CortexPro\\data\\som\\tmp\\som_04.lgg";

    // Data to plot
    // We want to plot Data - Close, using multiple colors in Close chart
    array arrX = CREATE_ARRAY(0);
    array arrY = CREATE_ARRAY(0);

    double nNetNum = 0;
    double nCounter = 0;
                    
    // We will use multiple images, so strXML's header and footer
    // are created outside of the Chart() function 
    string strXML = "<forex>\r\n";
    
    double nIterations = 2000; 
        
    array arrDim = CREATE_ARRAY(0);
    arrDim[0] = 50; arrDim[1] = 50; 
//    arrDim[2] = 100; arrDim[3] = 100;
//    arrDim[4] = 200; arrDim[5] = 200;
//    arrDim[6] = 500; arrDim[7] = 500;
//    arrDim[7] = 1000; arrDim[8] = 1000;
    
    double nExpectedCycles = ARRAY_SIZE(arrParameters) 
        * ARRAY_SIZE(arrStrLags) * ARRAY_SIZE(arrDim) / 2;

    double nRedrawEvery = 10;
    string strSomFileName = "..\\CortexPro\\data\\som\\nn\\som_04.kh";

    string strParam;
    string strToken;
    double nInterval;
    double nMa;

    // Before MA
    array arrNocRaw = CREATE_ARRAY(0);
    // Smoothed with MA
    array arrNoc = CREATE_ARRAY(0);

    double bStartLearning = 1;
    
    double nNumOfInputs;
    for(double nParIdx = 0; nParIdx < ARRAY_SIZE(arrParameters); 
        nParIdx = nParIdx + 1)
    {
        // Parse string with NOC parameters
        strParam = arrParameters[nParIdx];
        strToken = GET_TOKEN(strParam, ",");
        nInterval = STR2NUM(strToken);

        strToken = GET_TOKEN(strParam, ",");
        double dRange = STR2NUM(strToken);

        ARRAY_REMOVE(arrNoc, -1);
        arrNocRaw = CreateNoc(nInterval, dRange);
        
        strToken = GET_TOKEN(strParam, ",");
        nMa = STR2NUM(strToken);
        arrNoc = EXP_MVAVG(arrNocRaw, nMa);
    
        for(double nLagIdx = 0; nLagIdx < ARRAY_SIZE(arrStrLags); 
            nLagIdx = nLagIdx + 1)
        {
            double nNumOfLags;
            string strLagBuf = arrStrLags[nLagIdx];
        
            double dMin;
            double dMax;
    
            CreateLagFile(strLagBuf, nRemoveFirst);
                                        
            for(double nDim = 0; nDim < ARRAY_SIZE(arrDim); nDim = nDim + 2)
            {
                nCounter = nCounter + 1;
    
                PRINT("*********\r\n%.0f of ", nCounter, 
                    "%.0f\t\t", nExpectedCycles, 
                    "NOC: %s\r\n", arrParameters[nParIdx], 
                    "Lags: %s\r\n", arrStrLags[nLagIdx]);
        
                nNumOfInputs = 1 * ARRAY_SIZE(arrLags);
                CREATE_SOM(strSomFileName, bIsPathRelative, nNumOfInputs, 
                    nIterations, arrDim[nDim], arrDim[nDim + 1], nRedrawEvery);
    
                if(OPEN_SOM_DLG(strSomFileName, bIsPathRelative, strLagFileName, 
                    bIsPathRelative, "", 1, "", arrColumnNumbers, 
                    bStartLearning) == 0)
                {
                    BREAK nParIdx;
                }
            
                double hSom = OPEN_SOM(strSomFileName , bIsPathRelative);
                
                GET_SOM_WINNERS(hSom, strLagFileName, bIsPathRelative, 
                    "", 1, "", arrColumnNumbers, 100);
                double bMode = 1;
                array arrColors = APPLY_SOM(hSom, strLagFileName, bIsPathRelative, 
                    bMode, "", 1, "", arrColumnNumbers);
                
                Chart("eurusd_h1_" + NUM2STR(nCounter, "%03.0f"));
                
                CLOSE_SOM(hSom);
            }
        }
    }

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", 
        "chart_forex_som", "root", strXML);
        
    PRINT("%s\r\n", "Done");
}

// ------

array CreateNoc(double nInterval, double dMinRange)
{    
    array arrNormOnCondition = CREATE_ARRAY(0);
    array arrPeriodLow = CREATE_ARRAY(0);
    array arrPeriodHigh = CREATE_ARRAY(0);

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MV_MIN(arrLow, nInterval);
    array arrPeriodHigh = MV_MAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrNormOnCondition[i] = 0;
    }
    
    double dClose;
    double dLow;
    double dHigh;
    for(i = nInterval; i < nArraySize; i = i + 1)
    {
        dClose = arrClose[i];
        dLow = arrPeriodLow[i];
        dHigh = arrPeriodHigh[i];

        if(dHigh - dLow > dMinRange)
        {
            // / 2 + 1 to confine to 0...1 instead of -1...1
            arrNormOnCondition[i] = 
                (((dClose - dLow) - (dHigh - dClose))
                    / (dHigh - dLow)) / 2 + 0.5; 
        }
        else
        {
            arrNormOnCondition[i] = 
                (((dClose - dLow) - (dHigh - dClose))
                    / dMinRange) / 2 + 0.5; 
        }
    }

    return arrNormOnCondition;
}

// ------
void CreateLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strLagFileName, "wb", bIsPathRelative);
    F_PRINT(hFile, "%s", "X,Y");

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    ARRAY_REMOVE(arrLags, -1);
    ARRAY_REMOVE(arrColumnNumbers, -1);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrLags[i] = STR2NUM(strToken);

        F_PRINT(hFile, ",Y-%.0f", arrLags[i]); 
        arrColumnNumbers[i] = i;
    }
    F_PRINT(hFile, "%s", "\r\n");

    ARRAY_REMOVE(arrX, -1);
    ARRAY_REMOVE(arrY, -1);

    for(double i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        F_PRINT(hFile, "%.0f", i - nRemoveFirst + 1);
        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT    (hFile, ",%f", arrNoc[i - arrLags[j]]);      
        }

        arrX[i - nRemoveFirst] = arrDate[i];
        arrY[i - nRemoveFirst] = arrClose[i];
        
        F_PRINT(hFile, "%s", "\r\n");
    }

    F_CLOSE(hFile);
}
// ------
void Chart(string strForexName)
{
    strXML = strXML + 
        "\t<symbol>\r\n\t\t<symbol>\r\n" + 
        arrParameters[nParIdx] + "; \t\t" + 
        arrStrLags[nLagIdx] + "\t\r\n";
    strXML = strXML + "\t\t</symbol>\r\n";
    strXML = strXML + "\t\t" + 
        SAVE_CHART_SMOOTH(arrColors, 37000, 800, 0, 
        strImagePath + strForexName + ".png", arrX, arrY);
        
    strXML = strXML + "\t</symbol>\r\n";
}

Above is a small fragment of a resulting chart. Note, that it is very hard to say what use can we have for this type of a classification, but a) maybe, FFBP Neural Network can make sence out of it and b) it is just an example.

We can also use daily charts. Below is the nearly identical code for MSFT stock price.

som_04a.tsc

void main()
{
    OUT_CLEANUP();
    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    // Delete results of our previous experiments
    PRINT("%s\r\n", "Deleting image files...");

    array_s arrDirList = GET_DIR(strImagePath, 0, "*.png");
    for(double n = 0; n < ARRAY_SIZE(arrDirList); n = n + 1)
    {
        F_UNLINK(arrDirList[n]);
    }

    // ---

    double hFile;
    double i;

    // ***** Loading data
    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\stocks\\msft.txt";
    double bIsPathRelative = 1;

    array arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    PRINT("%s\r\n", "Loading data");

    TABLE_LOADER(strDataFileName, bIsPathRelative, 0, "", 1, 
        "<!-- chart1.finance.dcn.yahoo.com uncompressed Sun May  9 15:48:28 PDT 2004 -->", 0, 
        arrDate, 1, arrOpen, 2, arrHigh, 3, arrLow, 4, arrClose);

    array_s arrParameters = CREATE_ARRAY_S(0);
    arrParameters[0] = "128,0.012,2";
//    arrParameters[1] = "128,0.012,4";
//    arrParameters[2] = "128,0.012,8";
//    arrParameters[3] = "128,0.012,16";
//    arrParameters[4] = "128,0.012,32";
//    arrParameters[5] = "128,0.012,64";
//    arrParameters[6] = "128,0.012,128";
//    arrParameters[7] = "128,0.012,256";

    array arrLags = CREATE_ARRAY(0);
    array_s arrStrLags = CREATE_ARRAY_S(0);

    arrStrLags[0] = "17,1,2,3,4,6,8,10,12,14,16,20,24,30,34,38,46,54";

    array arrColumnNumbers = CREATE_ARRAY(0);

    double nRemoveFirst = 128;
    
    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\som_04a.lgg";

    array arrX = CREATE_ARRAY(0);
    array arrY = CREATE_ARRAY(0);

    double nNetNum = 0;
    double nCounter = 0;
                    
    string strXML = "<forex>\r\n";
    
    double nIterations = 5000; 
        
    array arrDim = CREATE_ARRAY(0);
    arrDim[0] = 50; arrDim[1] = 50; 
    
    double nExpectedCycles = ARRAY_SIZE(arrParameters) * 
        ARRAY_SIZE(arrStrLags) * ARRAY_SIZE(arrDim) / 2;

    double nRedrawEvery = 10;
    string strSomFileName = 
        "..\\CortexPro\\data\\som\\nn\\som_04a.kh";

    string strParam;
    string strToken;
    double nInterval;
    double nMa;
    array arrNocRaw = CREATE_ARRAY(0);
    array arrNoc = CREATE_ARRAY(0);

    double bStartLearning = 1;
    
    double nNumOfInputs;
    for(double nParIdx = 0; nParIdx < ARRAY_SIZE(arrParameters); 
        nParIdx = nParIdx + 1)
    {
        strParam = arrParameters[nParIdx];
        strToken = GET_TOKEN(strParam, ",");
        nInterval = STR2NUM(strToken);

        strToken = GET_TOKEN(strParam, ",");
        double dRange = STR2NUM(strToken);

        ARRAY_REMOVE(arrNoc, -1);
        arrNocRaw = CreateNoc(nInterval, dRange);
        
        strToken = GET_TOKEN(strParam, ",");
        nMa = STR2NUM(strToken);
        arrNoc = EXP_MVAVG(arrNocRaw, nMa);
    
        for(double nLagIdx = 0; nLagIdx < ARRAY_SIZE(arrStrLags); 
            nLagIdx = nLagIdx + 1)
        {
            double nNumOfLags;
            string strLagBuf = arrStrLags[nLagIdx];
        
            double dMin;
            double dMax;
    
            CreateLagFile(strLagBuf, nRemoveFirst);
                                        
            for(double nDim = 0; nDim < ARRAY_SIZE(arrDim); nDim = nDim + 2)
            {
                nCounter = nCounter + 1;
    
                PRINT("*********\r\n%.0f of ", nCounter, "%.0f\t\t", nExpectedCycles, 
                    "NOC: %s\r\n", arrParameters[nParIdx], 
                    "Lags: %s\r\n", arrStrLags[nLagIdx]);
        
                nNumOfInputs = 1 * ARRAY_SIZE(arrLags);
                CREATE_SOM(strSomFileName, bIsPathRelative, nNumOfInputs, 
                    nIterations, arrDim[nDim], arrDim[nDim + 1], nRedrawEvery);
    
                if(OPEN_SOM_DLG(strSomFileName, bIsPathRelative, 
                    strLagFileName, bIsPathRelative, "", 1, 
                    "<!-- chart1.finance.dcn.yahoo.com uncompressed Sun May  9 15:48:28 PDT 2004 -->", 
                    arrColumnNumbers, bStartLearning) == 0)
                {
                    BREAK nParIdx;
                }
            
                double hSom = OPEN_SOM(strSomFileName , bIsPathRelative);
                
                GET_SOM_WINNERS(hSom, strLagFileName, bIsPathRelative, "", 1, 
                    "<!-- chart1.finance.dcn.yahoo.com uncompressed Sun May  9 15:48:28 PDT 2004 -->", 
                    arrColumnNumbers, 1);
                double bMode = 1;
                array arrColors = APPLY_SOM(hSom, strLagFileName, 
                    bIsPathRelative, bMode, "", 1, 
                    "<!-- chart1.finance.dcn.yahoo.com uncompressed Sun May  9 15:48:28 PDT 2004 -->", 
                    arrColumnNumbers);
                
                Chart("msft_" + NUM2STR(nCounter, "%03.0f"));
                
                CLOSE_SOM(hSom);
            }
        }
    }

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", 
        "chart_forex_som", "root", strXML);
        
    PRINT("%s\r\n", "Done");
}

// ------

array CreateNoc(double nInterval, double dMinRange)
{    
    array arrNormOnCondition = CREATE_ARRAY(0);
    array arrPeriodLow = CREATE_ARRAY(0);
    array arrPeriodHigh = CREATE_ARRAY(0);

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MV_MIN(arrLow, nInterval);
    array arrPeriodHigh = MV_MAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrNormOnCondition[i] = 0;
    }
    
    double dClose;
    double dLow;
    double dHigh;
    for(i = nInterval; i < nArraySize; i = i + 1)
    {
        dClose = arrClose[i];
        dLow = arrPeriodLow[i];
        dHigh = arrPeriodHigh[i];

        if(dHigh - dLow > dMinRange)
        {
            // / 2 + 1 to confine to 0...1 instead of -1...1
            arrNormOnCondition[i] = 
                (((dClose - dLow) - (dHigh - dClose))
                    / (dHigh - dLow)) / 2 + 0.5; 
        }
        else
        {
            arrNormOnCondition[i] = 
                (((dClose - dLow) - (dHigh - dClose))
                    / dMinRange) / 2 + 0.5; 
        }
    }

    return arrNormOnCondition;
}

// ------
void CreateLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strLagFileName, "wb", bIsPathRelative);
    F_PRINT(hFile, "%s", "X,Y");

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    ARRAY_REMOVE(arrLags, -1);
    ARRAY_REMOVE(arrColumnNumbers, -1);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrLags[i] = STR2NUM(strToken);

        F_PRINT(hFile, ",Y-%.0f", arrLags[i]); 
        arrColumnNumbers[i] = i;
    }
    F_PRINT(hFile, "%s", "\r\n");

    ARRAY_REMOVE(arrX, -1);
    ARRAY_REMOVE(arrY, -1);

    for(double i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        F_PRINT(hFile, "%.0f", i - nRemoveFirst + 1);
        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT    (hFile, ",%f", arrNoc[i - arrLags[j]]);      
        }

        arrX[i - nRemoveFirst] = arrDate[i];
        arrY[i - nRemoveFirst] = arrClose[i];
        
        F_PRINT(hFile, "%s", "\r\n");
    }

    F_CLOSE(hFile);
}
// ------
void Chart(string strForexName)
{
    strXML = strXML + 
        "\t<symbol>\r\n\t\t<symbol>\r\n" + 
        arrParameters[nParIdx] + "; \t\t" + arrStrLags[nLagIdx] + "\t\r\n";
    strXML = strXML + "\t\t</symbol>\r\n";
    strXML = strXML + "\t\t" + SAVE_CHART_SMOOTH(arrColors, 1700, 800, 1, 
        strImagePath + strForexName + ".png", arrX, arrY);
        
    strXML = strXML + "\t</symbol>\r\n";
}

link to a large image

FOREX Trading Strategy without Kohonen SOM

With the chart we got in the chapter above, it is hard to create a forex trading system system. There is a classification, all right. But it does not look like "trend up - green, trent down - red". So let's use a FFBP neural network to mak sence out of it. To make it better, let's use both Self-Organizing Map output and NOC as FFBP NN inputs.

We are going to use the script from the article about Neural Networks FOREX Trading, that, I assume, you have already read. First, let's run the original, SOM-free, script again and find the optimal set of NN parameters.

som_05.tsc

void main()
{
    OUT_CLEANUP();

    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    PRINT("%s\r\n", "Deleting image files...");

    array_s arrDirList = GET_DIR(strImagePath, 0, "*.png");
    for(double n = 0; n < ARRAY_SIZE(arrDirList); n = n + 1)
    {
        F_UNLINK(arrDirList[n]);
    }

    string strForexName = "EURUSD_H1";

    string strNnFileName = "..\\CortexPro\\data\\som\\nn\\som_05";

    // ------------

    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\" 
            + strForexName + ".TXT";
    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\"
            + strForexName + "_nn_05.lgg";
    
    double bIsPathRelative = 1;

    array arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    TABLE_LOADER(strDataFileName, bIsPathRelative, 0, "", 0, "", 0, 
        arrDate, 1, arrTime, 2, arrOpen, 3, arrHigh, 
        4, arrLow, 5, arrClose);
    arrTime = arrTime / (24 * 60);
    arrDate = arrDate + arrTime;

    double nArraySize = ARRAY_SIZE(arrClose);
    double nExtractRecords = 0.7 * ARRAY_SIZE(arrClose);

    // -------
    
    // Interval, Range, Ma, OutLag 
    array_s arrParameters = CREATE_ARRAY_S(0);
    arrParameters[0] = "12,0.004,3,2";
//    arrParameters[1] = "24,0.004,3,2";
//    arrParameters[2] = "36,0.004,3,2";
//    arrParameters[3] = "48,0.004,3,2";

    array arrNeurons = CREATE_ARRAY(0);
    arrNeurons[0] = 3;
    arrNeurons[1] = 7;
    arrNeurons[2] = 10;
    arrNeurons[3] = 16;
    arrNeurons[4] = 20;

    array arrLags = CREATE_ARRAY(0);
    array_s arrStrLags = CREATE_ARRAY_S(0);
    
    // ATTN: These lags are based on nWinLag. 
    // Example: nWinLag = 1, then each number should be increased by 1
    arrStrLags[0] = "17,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16";

    array arrStopLoss = CREATE_ARRAY(0);
    arrStopLoss[0] = 0.02;
        
    array arrNoc;
    array arrNocSmooth;

    double nRemoveFirst = 200; // arrOutLag[ARRAY_SIZE(arrOutLag) - 1];

    double dStopError = 0;
    double nStopEpoch = 5000; 

    array arrNn = CREATE_ARRAY(0);

    string strParam;
    string strToken;
    double nMa;
    double hNn;


    // ------

    double nNetNum = 0;
    double nCounter = 0;
    double dExpectedCycles = ARRAY_SIZE(arrParameters)
        * ARRAY_SIZE(arrStrLags) * ARRAY_SIZE(arrNeurons);

    string strResult = "";
    
    double dErrorMin = -1;

    double bSuccess;
    double nWinners = 0;
    double nTradeNumber;

    array arrBalance = CREATE_ARRAY(0);
    array arrBalanceBuy = CREATE_ARRAY(0);
    array arrBalanceSell = CREATE_ARRAY(0);

    string strXML = "<forex>\r\n";
    
    double nImageNum = 0;

    for(double nParIdx = 0; nParIdx < ARRAY_SIZE(arrParameters); 
        nParIdx = nParIdx + 1)
    {
        strParam = arrParameters[nParIdx];
        strToken = GET_TOKEN(strParam, ",");
        double nInterval = STR2NUM(strToken);

        strToken = GET_TOKEN(strParam, ",");

        double dRange = STR2NUM(strToken);

        arrNoc = CreateNoc(nInterval, dRange);

        strToken = GET_TOKEN(strParam, ",");
        nMa = STR2NUM(strToken);

        arrNocSmooth = EXP_MVAVG(arrNoc, nMa);

        strToken = GET_TOKEN(strParam, ",");
        double nOutLag = STR2NUM(strToken);
        
        for(double nLagIdx = 0; nLagIdx < ARRAY_SIZE(arrStrLags); 
            nLagIdx = nLagIdx + 1)
        {
            double nNumOfLags;
            string strLagBuf = arrStrLags[nLagIdx];
            CreateLagFile(strLagBuf, nRemoveFirst);

            for(double nNeuronsIdx = 0; nNeuronsIdx < ARRAY_SIZE(arrNeurons); 
                nNeuronsIdx = nNeuronsIdx + 1)
            {
                nCounter = nCounter + 1;

                double nNeurons = arrNeurons[nNeuronsIdx];
                        
                PRINT("%.0f", nCounter, " of %.0f\r\n", dExpectedCycles);

                NewNn(arrLags, dStopError, nStopEpoch, nNeurons);
                TeachNn();

                hNn = OPEN_NN(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), 
                    bIsPathRelative);

                APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrNocSmooth, 
                    arrLags, 1, arrNn);

                for(double nStopIdx = 0; nStopIdx < ARRAY_SIZE(arrStopLoss); 
                    nStopIdx = nStopIdx + 1)
                {
                    double dStopLoss = arrStopLoss[nStopIdx];
                    double dTakeProfit = 0;
                                
                    for(double dStopIncrease = 0; dStopIncrease < 0.7; 
                        dStopIncrease = dStopIncrease + 0.1)
                    {
                        OUT_CLEANUP();

                        for(double nBuyIdx = 0; nBuyIdx < 30; nBuyIdx = nBuyIdx + 1)
                        {
                            nImageNum = nImageNum + 1;
                            
                            double dBuyLevel = 0.1 + 0.01 * nBuyIdx;

                            double dSellLevel = 0.9;

                            PRINT("%.0f", nCounter, " of %.0f", dExpectedCycles);

                            Test();
                                                                        
                            if(bSuccess != 0)
                            {
                                nWinners = nWinners + 1;
                                Chart(strForexName);
                            }
                            PRINT(" (%.0f)\r\n", nWinners);    
                        }
                    }
                }
            }
        }
    }

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", "chart_forex_som", "root", strXML);
//    SHOW_XML(strImagePath + "chart_forex_nn.xml");

    PRINT("%s\r\nDone\r\n", strResult);
}

// ---------------

void Test()
{
    bSuccess = 1;
    
    ARRAY_REMOVE(arrBalance, -1);
    arrBalance[0] = 1000;

    ARRAY_REMOVE(arrBalanceBuy, -1);
    arrBalanceBuy[0] = 0;

    ARRAY_REMOVE(arrBalanceSell, -1);
    arrBalanceSell[0] = 0;

    array arrBars = CREATE_ARRAY(0);
    arrBars[0] = 0;

    double dLotSize = 100;        // 0.1 lot
    double nType = 0;            // 1 buy, -1 sell, 0 - none

    double dSpread = 0.0005;
    double bStop = 0;

    double dMaxDrawDown = 0;            // Max. Drawdown
    double dCurrentMax = 1000;        // Used to calculate drawdown

    nTradeNumber = 0;
    double nTradeNumberBuy = 0;
    double nTradeNumberSell = 0;
    double dOpenPrice;
    double dStop;
    double dTp = 0;

    for(double nBar = nRemoveFirst + 1; 
        nBar < ARRAY_SIZE(arrClose) - 1; nBar = nBar + 1)
    {
        if(nType != 0)
        {
            bStop = 0;
            double dClosedAt;

            // If BUY and stop loss or take profit reached
            if(nType == -1 && (arrLow[nBar] <= dStop || (dTakeProfit > 0 
                && arrHigh[nBar] >= dTp - dSpread)))
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1]; 

                bStop = 1;
                dClosedAt = arrLow[nBar + 1];
            }
            else
            {
                if(nType == 1 && (arrHigh[nBar] >= dStop - dSpread ||
                    (dTakeProfit > 0 && arrLow[nBar] <= dTp)))
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] = 
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    bStop = 1;
                    dClosedAt = arrHigh[nBar + 1];
                }
            }

            if(bStop == 1)
            {
                nType = 0;
                arrBars[ARRAY_SIZE(arrBars)] = nBar;
            }
        }

        double dDrawDown = (dCurrentMax - 
            arrBalance[ARRAY_SIZE(arrBalance) - 1]) / 1000; //dCurrentMax; 
        dMaxDrawDown = MAX(dMaxDrawDown, dDrawDown);
        dCurrentMax = MAX(dCurrentMax, arrBalance[ARRAY_SIZE(arrBalance) - 1]);

        if(dMaxDrawDown > 1)
        {
            bSuccess = 0;
            break nBar;
        }

        if(nType != -1 && arrNn[nBar - 1] <= dBuyLevel && arrNn[nBar] >= dBuyLevel)
        {
            if(nType == 1)
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                arrBars[ARRAY_SIZE(arrBars)] = nBar;

                nType = 0;
            }

            dOpenPrice = arrHigh[nBar + 1];
            dStop = dOpenPrice - dStopLoss;
            dTp = dOpenPrice + dTakeProfit;
            nType = -1;
            
            nTradeNumber = nTradeNumber + 1;
            nTradeNumberBuy = nTradeNumberBuy + 1;
        }
        else
        {
            if(nType != 1 && arrNn[nBar - 1] >= dSellLevel && arrNn[nBar] <= dSellLevel)
            {
                if(nType == -1)
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] =
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] =
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] =
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];

                    arrBars[ARRAY_SIZE(arrBars)] = nBar;
                
                    nType = 0;
                }
            }
        }

        if(dStopIncrease != 0)
        {
            if(nType == -1)
            {
                if(arrLow[nBar] - dStop >= dStopLoss * (1 + dStopIncrease))
                {
                    dStop = arrLow[nBar] - dStopLoss;
                }
            }
            else
            {
                if(nType == 1)
                {
                    if(dStop - arrHigh[nBar] >= dStopLoss * (1 + dStopIncrease))
                    {
                        dStop = arrHigh[nBar] + dStopLoss;
                    }
                }
            }
        }
    }

    // If at the end we have open positions, close them

    if(nType == 1)
    {
        arrBalance[ARRAY_SIZE(arrBalance)] = 
            arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

        arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;
    }
    else
    {
        if(nType == -1)
        {
            arrBalance[ARRAY_SIZE(arrBalance)] = 
                arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];
    
            arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;

        }
    }

    if(ARRAY_SIZE(arrBalance) <= 1 || 
        (arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] <= 500 
            && arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] <= 200) ||
        nTradeNumber < 20)
    {
        bSuccess = 0;
    }
}

// ---------------

array CreateNoc(double nInterval, double dMinRange)
{    
    PRINT("%s\r\n", "Creating NOC indicator");

    array arrNormOnCondition = CREATE_ARRAY(0);
    array arrPeriodLow = CREATE_ARRAY(0);
    array arrPeriodHigh = CREATE_ARRAY(0);

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MV_MIN(arrLow, nInterval);
    array arrPeriodHigh = MV_MAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrNormOnCondition[i] = 0;
    }
    
    double dClose;
    double dLow;
    double dHigh;
    for(i = nInterval; i < nArraySize; i = i + 1)
    {
        dClose = arrClose[i];
        dLow = arrPeriodLow[i];
        dHigh = arrPeriodHigh[i];

        if(dHigh - dLow > dMinRange)
        {
            // / 2 + 1 to confine to 0...1 instead of -1...1
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / (dHigh - dLow)) / 2 + 0.5; 
        }
        else
        {
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / dMinRange) / 2 + 0.5; 
        }
    }

    return arrNormOnCondition;
}

// ----------------

void CreateLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strLagFileName, "wb", bIsPathRelative);

    F_PRINT(hFile, "%s", "No,Clv");

    ARRAY_REMOVE(arrLags, -1);

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrLags[i] = STR2NUM(strToken) + nOutLag;
    }

    double nFullLag;
    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        nFullLag = arrLags[i];
        F_PRINT(hFile, ",NocMa%.0f", nMa, "-%.0f", nFullLag); 
    }

    F_PRINT(hFile, "%s\r\n", "");

    double nNum;
    for(i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        nNum = i - nRemoveFirst + 1;
        F_PRINT(hFile, "%.0f", nNum, ",%f", arrNocSmooth[i]);     

        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrNocSmooth[i - arrLags[j]]);     
        }

        F_PRINT(hFile, "%s\r\n", "");
    }

    F_CLOSE(hFile);
}

// --------------

void NewNn(array arrLags, double dStopError, 
    double nStopEpoch, double nNeuronsLayer2)
{
    double nSkipBefore = 0;
    double nSkipAfter = 0;
    string strStartLine = "";
    string strEndLine = "";
    double bReverseArrays = 0;

    // Inputs
    array arrInputColumns = CREATE_ARRAY(0);
    array_s arrInputColumnNames = CREATE_ARRAY_S(0);
    
    // 0  - Number, 1 - Clv, our input begins at column No 2
    for(double nInputCol = 0; nInputCol < ARRAY_SIZE(arrLags); 
        nInputCol = nInputCol + 1) 
    {
        arrInputColumns[nInputCol] = nInputCol + 2;
        arrInputColumnNames[nInputCol] = 
            "Clv-" + NUM2STR(arrLags[nInputCol], "%.0f"); 
    }

    array arrOutputColumns = CREATE_ARRAY(0);
    arrOutputColumns[0] = 1;    // Noc

    array_s arrOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutputColumnNames[0] = "Noc";

    double nNeuronsLayer1 = ARRAY_SIZE(arrLags);
//    double nNeuronsLayer2 = 7;
    double nNeuronsLayer3 = 1;
    double nNeuronsLayer4 = 0;
    double nLayers = 3; 
    double nActivation = 0;
    double nAdjustRange = 1.0;

    array arrOutTabInputColumns = CREATE_ARRAY(0);
    arrOutTabInputColumns[0] = 0;    // Number

    array_s arrOutTabInputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabInputColumnNames[0] = "No";

    array arrOutTabOutputColumns = CREATE_ARRAY(0);
    // Desired output and NN output will be added to the 
    // same list, right after inputs
    arrOutTabOutputColumns[0] = ARRAY_SIZE(arrLags) + 2;    
    arrOutTabOutputColumns[1] = ARRAY_SIZE(arrLags) + 3;

    array_s arrOutTabOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabOutputColumnNames[0] = "Noc"; 
    arrOutTabOutputColumnNames[1] = "NN: Noc" ;

    CREATE_NN(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), 
        bIsPathRelative, strLagFileName, bIsPathRelative, nSkipBefore, 
        nSkipAfter, strStartLine, strEndLine,
        bReverseArrays, arrInputColumns, arrInputColumnNames, 
        arrOutputColumns, arrOutputColumnNames, nExtractRecords, 
        dStopError, nStopEpoch, nNeuronsLayer1, nNeuronsLayer2, 
        nNeuronsLayer3, nNeuronsLayer4, nLayers, nActivation, nAdjustRange, 
        arrOutTabInputColumns, arrOutTabInputColumnNames,
        arrOutTabOutputColumns, arrOutTabOutputColumnNames);
}

// ----------------

void TeachNn()
{
    PRINT("%s\r\n", "Opening NN dialog, teaching the NN");

    double bStartLearning = 1; 
    double bResumeScript = 1;
    double bReset = 1;

    OPEN_NN_FILE(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), 
        bIsPathRelative, bStartLearning, bResumeScript, bReset);
}


// -------------

void Chart(string strForexName)
{
    string strWinNnFileName;
        
    string strImageFileName = strImagePath + strForexName + 
        NUM2STR(nImageNum, "_%.0f") + "_nn_05.png";    
            
    strXML = strXML + "\t<symbol>\r\n\t\t<symbol>\r\n";

    strXML = strXML + "NN: " + NUM2STR(nCounter, "%.0f, ")
        + "Trades: " + NUM2STR(nTradeNumber, "%.0f")
        + "(Buy: " + NUM2STR(nTradeNumberBuy, "%.0f")
        + ", Sell :" + NUM2STR(nTradeNumberSell, "%.0f)\r\n")
        + "NocInterval: " + NUM2STR(nInterval, "%.0f")
        + ", Range: " + NUM2STR(dRange, "%.3f")
        + ", Ma: " + NUM2STR(nMa, "%.0f\r\n")

        + "Lag: " + NUM2STR(nOutLag, "%.0f")
        + ", Neurons: " + NUM2STR(nNeurons, "%.0f\r\n")
        + "Stop: " + NUM2STR(dStopLoss, "%.4f")
        + ", Tp: " + NUM2STR(dTakeProfit, "%.4f")
        + NUM2STR(dStopIncrease, ", Stop increase: %f")
        + ", Buy: " + NUM2STR(dBuyLevel, "%.3f")
        + ", Sell: " + NUM2STR(dSellLevel, "%.3f\r\n")

        + "Drawdown: " + NUM2STR(dMaxDrawDown, "%.3f\r\n")

        + "Profit: " + NUM2STR(arrBalance[ARRAY_SIZE(arrBalance) - 1] - 1000, "%f")
        + " (long: " + NUM2STR(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1], "%f")
        + ", short: " + NUM2STR(arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1], "%f)\r\n");

    strXML = strXML + "\t\t</symbol>\r\n";

    strXML = strXML + "\t\t" + SAVE_CHART(400, 300, 0, strImageFileName, 
        arrBars, arrBalance, arrBalanceBuy, arrBalanceSell);
    strXML = strXML + "\t</symbol>\r\n";
}

Here, we can choose a winner. Note, that beating the market is not our goal here, what we want to do, is to take more-or-less working FFBP system and to see, if its performance can be improved by supplying additional inputs from SOM.



Trades: 27(Buy: 27, Sell :0)
NocInterval: 12, Range: 0.004, Ma: 3 Lag: 2,
Neurons: 5
Stop: 0.0200, Tp: 0.0000, Stop increase: 0.600000,
Buy: 0.170, Sell: 0.900
Drawdown: 0.299 Profit: 6174.000000 (long: 6174.000000, short: 0.000000)

This chart looks good enough. In the next chapter we are going to use Self-Organizing Map output in addition to NOC.

Using Kohonen Neural Network and FFBP Neural Network together

We are going to remove cycles from the previous example, and to add a Self-Organizing Map to it. The Kohonen SOM was created by som_04.tsc, note, that you need to run that script first. So, first of all, run som_04.tsc and rename the resulting Self-Organizing Map to som_04_winner.kh.

The following script uses the existing Kohonen SOM and tries to find optimal parameters for FFBP NN that uses its output as an input, in addition to NOC.

som_07.tsc

void main()
{
    OUT_CLEANUP();
    
    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    PRINT("%s\r\n", "Deleting image files...");

    array_s arrDirList = GET_DIR(strImagePath, 0, "*.png");
    for(double n = 0; n < ARRAY_SIZE(arrDirList); n = n + 1)
    {
        F_UNLINK(arrDirList[n]);
    }

    // ------ First, let's handle SOM
    
    double hFile;
    double i;

    // ***** Loading data
    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\eurusd_h1.txt";
    double bIsPathRelative = 1;

    array arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    PRINT("%s\r\n", "Loading data");

    TABLE_LOADER(strDataFileName, bIsPathRelative, 0, "", 0, "", 0, 
        arrDate, 1, arrTime, 2, arrOpen, 3, arrHigh, 4, arrLow, 5, arrClose);
    arrTime = arrTime / (24 * 60);
    arrDate = arrDate + arrTime;

    array_s arrSomParameters = CREATE_ARRAY_S(0);
    arrSomParameters[0] = "128,0,2,2";    

    array arrSomLags = CREATE_ARRAY(0);
    array_s arrSomStrLags = CREATE_ARRAY_S(0);

    arrSomStrLags[0] = "17,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16";

    array arrColumnNumbers = CREATE_ARRAY(0);
    
    double nRemoveFirst = 200;
    
    string strSomLagFileName = "..\\CortexPro\\data\\som\\tmp\\som_07.lgg";
    string strSomFileName = "..\\CortexPro\\data\\som\\nn\\som_07.kh";

    string strSomParam;
    string strSomToken;
    double nSomInterval;
    double nSomMa;
    array arrSomNocRaw = CREATE_ARRAY(0);
    array arrSomNoc = CREATE_ARRAY(0);

    double nSomParIdx = 0;

    strSomParam = arrSomParameters[nSomParIdx];
    strSomToken = GET_TOKEN(strSomParam, ",");
    nSomInterval = STR2NUM(strSomToken);

    strSomToken = GET_TOKEN(strSomParam, ",");
    double dSomRange = STR2NUM(strSomToken);

    ARRAY_REMOVE(arrSomNoc, -1);
    arrSomNocRaw = CreateNoc(nSomInterval, dSomRange);
        
    strSomToken = GET_TOKEN(strSomParam, ",");
    nSomMa = STR2NUM(strSomToken);
    arrSomNoc = EXP_MVAVG(arrSomNocRaw, nSomMa);
        
    strSomToken = GET_TOKEN(strSomParam, ",");
    double nSomOutLag = STR2NUM(strSomToken);
            
    double nSomLagIdx = 0;
    double nSomNumOfLags;
    string strSomLagBuf = arrSomStrLags[nSomLagIdx];
        
    CreateSomLagFile(strSomLagBuf, nRemoveFirst);
                                        
    double hSom = OPEN_SOM(strSomFileName , bIsPathRelative);
                
    GET_SOM_WINNERS(hSom, strSomLagFileName, 
        bIsPathRelative, "", 1, "", arrColumnNumbers, 1);

    double bMode = 1;
    array arrColors = APPLY_SOM(hSom, strSomLagFileName, bIsPathRelative, 
        bMode, "", 1, "", arrColumnNumbers);
                
    CLOSE_SOM(hSom);

    // ------ Now let's handle FFBP NN
        
    string strForexName = "EURUSD_H1";
    string strNnFileName = "..\\CortexPro\\data\\som\\nn\\som_07";

    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\" 
            + strForexName + ".TXT";
    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\"
            + strForexName + "_nn_07.lgg";
    
    double nArraySize = ARRAY_SIZE(arrClose);

    // ---
    
    // Interval, Range, Ma, OutLag 
    array_s arrParameters = CREATE_ARRAY_S(0);
    arrParameters[0] = "12,0,3,2";

    array arrNeurons = CREATE_ARRAY(0);
    arrNeurons[0] = 5;
    arrNeurons[1] = 7;
    arrNeurons[2] = 10;
    arrNeurons[3] = 16;

    array arrLags = CREATE_ARRAY(0);
    array_s arrStrLags = CREATE_ARRAY_S(0);
    
    // ATTN: These lags are based on nWinLag. 
    // Example: nWinLag = 1, then each number should be increased by 1
    arrStrLags[0] = "17,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16";

    array arrStopLoss = CREATE_ARRAY(0);
    arrStopLoss[0] = 0.002;
    arrStopLoss[1] = 0.005;
    arrStopLoss[2] = 0.0075;
    arrStopLoss[3] = 0.01;
    arrStopLoss[4] = 0.015;
    arrStopLoss[5] = 0.02;

    array arrNoc;
    array arrNocSmooth;

    double dStopError = 0;
    double nStopEpoch = 5000; 

    array arrNn = CREATE_ARRAY(0);

    string strParam;
    string strToken;
    double nMa;
    double hNn;
    double nExtractRecords;

    // ------

    double nNetNum = 0;
    double nCounter = 0;
    double dExpectedCycles = ARRAY_SIZE(arrParameters)
        * ARRAY_SIZE(arrStrLags) * ARRAY_SIZE(arrNeurons);

    string strResult = "";
    
    double dErrorMin = -1;

    double bSuccess;
    double nWinners = 0;
    double nTradeNumber;

    array arrBalance = CREATE_ARRAY(0);
    array arrBalanceBuy = CREATE_ARRAY(0);
    array arrBalanceSell = CREATE_ARRAY(0);

    string strXML = "<forex>\r\n";
    
    double nImageNum = 0;

    for(double nParIdx = 0; nParIdx < ARRAY_SIZE(arrParameters); 
        nParIdx = nParIdx + 1)
    {
        strParam = arrParameters[nParIdx];
        strToken = GET_TOKEN(strParam, ",");
        double nInterval = STR2NUM(strToken);

        strToken = GET_TOKEN(strParam, ",");

        double dRange = STR2NUM(strToken);

        arrNoc = CreateNoc(nInterval, dRange);

        strToken = GET_TOKEN(strParam, ",");
        nMa = STR2NUM(strToken);

        arrNocSmooth = EXP_MVAVG(arrNoc, nMa);

        strToken = GET_TOKEN(strParam, ",");
        double nOutLag = STR2NUM(strToken);
        
        for(double nLagIdx = 0; nLagIdx < ARRAY_SIZE(arrStrLags); 
            nLagIdx = nLagIdx + 1)
        {
            double nNumOfLags;
            string strLagBuf = arrStrLags[nLagIdx];
            CreateLagFile(strLagBuf, nRemoveFirst);

            for(double nNeuronsIdx = 0; nNeuronsIdx < ARRAY_SIZE(arrNeurons); 
                nNeuronsIdx = nNeuronsIdx + 1)
            {
                nCounter = nCounter + 1;

                double nNeurons = arrNeurons[nNeuronsIdx];
                        
                PRINT("%.0f", nCounter, " of %.0f\r\n", dExpectedCycles);

                NewNn(arrLags, dStopError, nStopEpoch, nNeurons);
                TeachNn();

                hNn = OPEN_NN(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), 
                    bIsPathRelative);

                APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrNocSmooth, arrLags, 
                    1, arrNn);

                for(double nStopIdx = 0; nStopIdx < ARRAY_SIZE(arrStopLoss); 
                    nStopIdx = nStopIdx + 1)
                {
                    double dStopLoss = arrStopLoss[nStopIdx];
                    double dTakeProfit = 0;
                                
                    for(double dStopIncrease = 0; dStopIncrease < 0.7; 
                        dStopIncrease = dStopIncrease + 0.1)
                    {
                        OUT_CLEANUP();

                        for(double nBuyIdx = 0; nBuyIdx < 30; nBuyIdx = nBuyIdx + 1)
                        {
                            nImageNum = nImageNum + 1;
                            
                            double dBuyLevel = 0.1 + 0.01 * nBuyIdx;

//                            for(double nSellIdx = 0; nSellIdx < 30; nSellIdx = nSellIdx + 1)
//                            {
//                                double dSellLevel = 0.9 - 0.01 * nSellIdx;
                                double dSellLevel = 0.9;

                                PRINT("%.0f", nCounter, " of %.0f", dExpectedCycles);

                                Test();
                                                                        
                                if(bSuccess == 0)
                                {
                                    PRINT(" (%.0f)\r\n", nWinners);
//                                    continue nSellIdx;
                                    continue nBuyIdx;
                                }
    
                                nWinners = nWinners + 1;
                                PRINT(" (%.0f)\r\n", nWinners);    
                                                
                                Chart(strForexName);
//                            }
                        }
                    }
                }
            }
        }
    }

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", "chart_forex_som", "root", strXML);
//    SHOW_XML(strImagePath + "chart_forex_nn.xml");

    PRINT("%s\r\nDone\r\n", strResult);
}

// ---------------

void Test()
{
    bSuccess = 1;
    
    ARRAY_REMOVE(arrBalance, -1);
    arrBalance[0] = 1000;

    ARRAY_REMOVE(arrBalanceBuy, -1);
    arrBalanceBuy[0] = 0;

    ARRAY_REMOVE(arrBalanceSell, -1);
    arrBalanceSell[0] = 0;

    array arrBars = CREATE_ARRAY(0);
    arrBars[0] = 0;

    double dLotSize = 100;        // 0.1 lot
    double nType = 0;            // 1 buy, -1 sell, 0 - none

    double dSpread = 0.0005;
    double bStop;

    double dMaxDrawDown = 0;            // Max. Drawdown
    double dCurrentMax = 1000;        // Used to calculate drawdown

    nTradeNumber = 0;
    double nTradeNumberBuy = 0;
    double nTradeNumberSell = 0;
    double dOpenPrice;
    double dStop;
    double dTp = 0;

    for(double nBar = nRemoveFirst + 1; 
        nBar < ARRAY_SIZE(arrClose) - 1; nBar = nBar + 1)
    {
        if(nType != 0)
        {
            bStop = 0;
            double dClosedAt;

            // If BUY and stop loss or take profit reached
            if(nType == -1 && (arrLow[nBar] <= dStop || (dTakeProfit > 0 
                && arrHigh[nBar] >= dTp - dSpread)))
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1]; 

                bStop = 1;
                dClosedAt = arrLow[nBar + 1];
            }
            else
            {
                if(nType == 1 && (arrHigh[nBar] >= dStop - dSpread ||
                    (dTakeProfit > 0 && arrLow[nBar] <= dTp)))
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] = 
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    bStop = 1;
                    dClosedAt = arrHigh[nBar + 1];
                }
            }

            if(bStop == 1)
            {
                nType = 0;
                arrBars[ARRAY_SIZE(arrBars)] = nBar;
            }
        }

        double dDrawDown = (dCurrentMax - 
            arrBalance[ARRAY_SIZE(arrBalance) - 1]) / 1000; //dCurrentMax; 
        dMaxDrawDown = MAX(dMaxDrawDown, dDrawDown);
        dCurrentMax = MAX(dCurrentMax, arrBalance[ARRAY_SIZE(arrBalance) - 1]);

        if(dMaxDrawDown > 1)
        {
            bSuccess = 0;
            break nBar;
        }

        if(nType != -1 && arrNn[nBar - 1] <= dBuyLevel && arrNn[nBar] >= dBuyLevel)
        {
            if(nType == 1)
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                arrBars[ARRAY_SIZE(arrBars)] = nBar;

                nType = 0;
            }

            dOpenPrice = arrHigh[nBar + 1];
            dStop = dOpenPrice - dStopLoss;
            dTp = dOpenPrice + dTakeProfit;
            nType = -1;
            
            nTradeNumber = nTradeNumber + 1;
            nTradeNumberBuy = nTradeNumberBuy + 1;
        }
        else
        {
            if(nType != 1 && arrNn[nBar - 1] >= dSellLevel && arrNn[nBar] <= dSellLevel)
            {
                if(nType == -1)
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] =
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] =
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] =
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];

                    arrBars[ARRAY_SIZE(arrBars)] = nBar;
                
                    nType = 0;
                }
            }
        }

        if(dStopIncrease != 0)
        {
            if(nType == -1)
            {
                if(arrLow[nBar] - dStop >= dStopLoss * (1 + dStopIncrease))
                {
                    dStop = arrLow[nBar] - dStopLoss;
                }
            }
            else
            {
                if(nType == 1)
                {
                    if(dStop - arrHigh[nBar] >= dStopLoss * (1 + dStopIncrease))
                    {
                        dStop = arrHigh[nBar] + dStopLoss;
                    }
                }
            }
        }
    }

    // If at the end we have open positions, close them

    if(nType == 1)
    {
        arrBalance[ARRAY_SIZE(arrBalance)] = 
            arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

        arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;
    }
    else
    {
        if(nType == -1)
        {
            arrBalance[ARRAY_SIZE(arrBalance)] = 
                arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];
    
            arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;

        }
    }

    if(ARRAY_SIZE(arrBalance) <= 1 || 
        (arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] <= 500 
            && arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] <= 200) ||
        nTradeNumber < 20)
    {
        bSuccess = 0;
    }
}

// ---------------

array CreateNoc(double nInterval, double dMinRange)
{    
    PRINT("%s\r\n", "Creating NOC indicator");

    array arrNormOnCondition = CREATE_ARRAY(0);
    array arrPeriodLow = CREATE_ARRAY(0);
    array arrPeriodHigh = CREATE_ARRAY(0);

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MV_MIN(arrLow, nInterval);
    array arrPeriodHigh = MV_MAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrNormOnCondition[i] = 0;
    }
    
    double dClose;
    double dLow;
    double dHigh;
    for(i = nInterval; i < nArraySize; i = i + 1)
    {
        dClose = arrClose[i];
        dLow = arrPeriodLow[i];
        dHigh = arrPeriodHigh[i];

        if(dHigh - dLow > dMinRange)
        {
            // / 2 + 1 to confine to 0...1 instead of -1...1
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / (dHigh - dLow)) / 2 + 0.5; 
        }
        else
        {
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / dMinRange) / 2 + 0.5; 
        }
    }

    return arrNormOnCondition;
}

// ------

void CreateSomLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strSomLagFileName, "wb", bIsPathRelative);
    F_PRINT(hFile, "%s", "X,Y");

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    ARRAY_REMOVE(arrSomLags, -1);
    ARRAY_REMOVE(arrColumnNumbers, -1);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrSomLags[i] = STR2NUM(strToken);

        F_PRINT(hFile, ",Y-%.0f", arrSomLags[i]); 
        arrColumnNumbers[i] = i;
    }
    F_PRINT(hFile, "%s", "\r\n");

    for(double i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        F_PRINT(hFile, "%.0f", i - nRemoveFirst + 1);
        for(double j = 0; j < ARRAY_SIZE(arrSomLags); j = j + 1)
        {
            F_PRINT    (hFile, ",%f", arrSomNoc[i - arrSomLags[j]]);      
        }

        F_PRINT(hFile, "%s", "\r\n");
    }

    F_CLOSE(hFile);
}


// ------

void CreateLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strLagFileName, "wb", bIsPathRelative);

    F_PRINT(hFile, "%s", "No,Clv");

    ARRAY_REMOVE(arrLags, -1);

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrLags[i] = STR2NUM(strToken) + nOutLag;
    }

    double nFullLag;
    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        nFullLag = arrLags[i];
        F_PRINT(hFile, ",NocMa%.0f", nMa, "-%.0f", nFullLag); 
    }
    
    F_PRINT(hFile, "red,green,blue%s\r\n", "");

    double nNum;
    for(i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        nNum = i - nRemoveFirst + 1;
        F_PRINT(hFile, "%.0f", nNum, ",%f", arrNocSmooth[i]);     

        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrNocSmooth[i - arrLags[j]]);     
        }

        for(j = 0; j < 3; j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrColors[3 * (i - arrLags[j]) + j]);     
        }

        F_PRINT(hFile, "%s\r\n", "");
    }

    F_CLOSE(hFile);
}

// --------------

void NewNn(array arrLags, double dStopError, double nStopEpoch, 
    double nNeuronsLayer2)
{
    double nSkipBefore = 0;
    double nSkipAfter = 0;
    string strStartLine = "";
    string strEndLine = "";
    double bReverseArrays = 0;

    // Inputs
    array arrInputColumns = CREATE_ARRAY(0);
    array_s arrInputColumnNames = CREATE_ARRAY_S(0);
    
    // 0  - Number, 1 - Clv, our input begins at column No 2
    for(double nInputCol = 0; nInputCol < ARRAY_SIZE(arrLags); 
        nInputCol = nInputCol + 1) 
    {
        arrInputColumns[nInputCol] = nInputCol + 2;
        arrInputColumnNames[nInputCol] = 
            "Clv-" + NUM2STR(arrLags[nInputCol], "%.0f"); 
    }

    array arrOutputColumns = CREATE_ARRAY(0);
    arrOutputColumns[0] = 1;    // Noc

    array_s arrOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutputColumnNames[0] = "Noc";

    nExtractRecords = 0.7 * ARRAY_SIZE(arrClose);

    double nNeuronsLayer1 = ARRAY_SIZE(arrLags);
//    double nNeuronsLayer2 = 7;
    double nNeuronsLayer3 = 1;
    double nNeuronsLayer4 = 0;
    double nLayers = 3; 
    double nActivation = 0;
    double nAdjustRange = 1.0;

    array arrOutTabInputColumns = CREATE_ARRAY(0);
    arrOutTabInputColumns[0] = 0;    // Number

    array_s arrOutTabInputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabInputColumnNames[0] = "No";

    array arrOutTabOutputColumns = CREATE_ARRAY(0);
    // Desired output and NN output will be added to the 
    // same list, right after inputs
    arrOutTabOutputColumns[0] = ARRAY_SIZE(arrLags) + 2;    
    arrOutTabOutputColumns[1] = ARRAY_SIZE(arrLags) + 3;

    array_s arrOutTabOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabOutputColumnNames[0] = "Noc"; 
    arrOutTabOutputColumnNames[1] = "NN: Noc" ;

    CREATE_NN(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), bIsPathRelative, 
        strLagFileName, bIsPathRelative, nSkipBefore, nSkipAfter, 
        strStartLine, strEndLine,
        bReverseArrays, arrInputColumns, arrInputColumnNames, 
        arrOutputColumns, arrOutputColumnNames, nExtractRecords, 
        dStopError, nStopEpoch, 
        nNeuronsLayer1, nNeuronsLayer2, nNeuronsLayer3, 
        nNeuronsLayer4, nLayers, 
        nActivation, nAdjustRange, arrOutTabInputColumns, 
        arrOutTabInputColumnNames,
        arrOutTabOutputColumns, arrOutTabOutputColumnNames);
}

// ----------------

void TeachNn()
{
    PRINT("%s\r\n", "Opening NN dialog, teaching the NN");

    double bStartLearning = 1; 
    double bResumeScript = 1;
    double bReset = 1;

    OPEN_NN_FILE(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), 
        bIsPathRelative, bStartLearning, bResumeScript, bReset);
}


// -------------

void Chart(string strForexName)
{
    string strWinNnFileName;
        
    string strImageFileName = strImagePath + strForexName + 
        NUM2STR(nImageNum, "_%.0f") + "_nn_07.png";    
            
    strXML = strXML + "\t<symbol>\r\n\t\t<symbol>\r\n";

    strXML = strXML + "NN: " + NUM2STR(nCounter, "%.0f, ")
        + "Trades: " + NUM2STR(nTradeNumber, "%.0f")
        + "(Buy: " + NUM2STR(nTradeNumberBuy, "%.0f")
        + ", Sell :" + NUM2STR(nTradeNumberSell, "%.0f)\r\n")
        + "NocInterval: " + NUM2STR(nInterval, "%.0f")
        + ", Range: " + NUM2STR(dRange, "%.3f")
        + ", Ma: " + NUM2STR(nMa, "%.0f\r\n")

        + "Lag: " + NUM2STR(nOutLag, "%.0f")
        + ", Neurons: " + NUM2STR(nNeurons, "%.0f\r\n")
        + "Stop: " + NUM2STR(dStopLoss, "%.4f")
        + ", Tp: " + NUM2STR(dTakeProfit, "%.4f")
        + NUM2STR(dStopIncrease, ", Stop increase: %f")
        + ", Buy: " + NUM2STR(dBuyLevel, "%.3f")
        + ", Sell: " + NUM2STR(dSellLevel, "%.3f\r\n")

        + "Drawdown: " + NUM2STR(dMaxDrawDown, "%.3f\r\n")

        + "Profit: " + NUM2STR(arrBalance[ARRAY_SIZE(arrBalance) - 1] - 1000, "%f")
        + " (long: " + NUM2STR(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1], "%f")
        + ", short: " + NUM2STR(arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1], "%f)\r\n");

    strXML = strXML + "\t\t</symbol>\r\n";

    strXML = strXML + "\t\t" + SAVE_CHART(400, 300, 0, strImageFileName, 
        arrBars, arrBalance, arrBalanceBuy, arrBalanceSell);
    strXML = strXML + "\t</symbol>\r\n";
}



Trades: 29(Buy: 29, Sell :0)
NocInterval: 12, Range: 0.000, Ma: 3 Lag: 2,
Neurons: 7 Stop: 0.0200, Tp: 0.0000, Stop increase: 0.500000,
Buy: 0.140, Sell: 0.900
Drawdown: 0.557 Profit: 6677.000000 (long: 6677.000000, short: 0.000000)

As you can see, at least one chart is better, than it was without SOM, though AGAIN, this is just an example, and for a real trading system, NOC is, most likely, NOT the best choice of input for Kohonen SOM (it is very good for FFBP NN, however).

Removing cycles from FFBP Neural Network Trading System

Now, let's port our trading system to a "real" trading platform. After all, Cortex Neural Networks Software cannot do online trading, so we need to use MetaTrader, TradStation or something else. We are going to create a script for MetaTrader "expert" here, that uses both our FFBP NN and SOM.

We do it in two steps. First, we are going to repeat the steps from Neural Networks FOREX trading article, for our winner FFBP. Then we add SOM.

To do it, we remove cycles from the som_05.tsc code, it leaves us with a code, that uses a single winning NN.
Then we add code to this script, to PRINT weights of neurons. We format this output, to make it comply with MetaTrader's (or other trading platform, it is up to you) syntax. As the result, we have a large array with NN weights, that we can paste into the expert/indicator code of a trading platform of your choice.

For now, let's remove all unnecessary cycles from som_5.tsc, so that we have the script, that works with a winner NN. As we use existng winning NN, we need to get it somewhere. The Cortex comes with som_05_winner_1.nn (created in som_05.tsc and then renamed).

Note, that the code still has cycles - just to be as familiar as possible - but these cycles are only done once. Also, some parameters for the Noc and network are hardcoded:

// NN: 1, Trades: 32(Buy: 32, Sell :0) 
// NocInterval: 12, Range: 0.004, Ma: 3 Lag: 2, 
// Neurons: 3 
// Stop: 0.0200, Tp: 0.0000, Stop increase: 0.600000, 
// Buy: 0.160, Sell: 0.900 
// Drawdown: 0.915 Profit: 5757.000000 (long: 5757.000000, short: 0.000000) 

arrParameters[0] = "12,0.004,3,2";
arrNeurons[0] = 3;
double dStopIncrease = 0.6;
double dBuyLevel = 0.160;

som_06.tsc, FFBP, no SOM yet

void main()
{
    OUT_CLEANUP();

    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    PRINT("%s\r\n", "Deleting image files...");

    array_s arrDirList = GET_DIR(strImagePath, 0, "*.png");
    for(double n = 0; n < ARRAY_SIZE(arrDirList); n = n + 1)
    {
        F_UNLINK(arrDirList[n]);
    }

    string strForexName = "EURUSD_H1";

    string strNnFileName = "..\\CortexPro\\data\\som\\nn\\som_06";

    // ------------

    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\" 
            + strForexName + ".TXT";
    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\"
            + strForexName + "_nn_06.lgg";
    
    double bIsPathRelative = 1;

    array arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    TABLE_LOADER(strDataFileName, bIsPathRelative, 0, "", 0, "", 0, 
        arrDate, 1, arrTime, 2, arrOpen, 3, arrHigh, 
        4, arrLow, 5, arrClose);
        
    // Turn date/time into a smooth array

array_s arrsDate = CREATE_ARRAY_S(0);
array_s arrsTime = CREATE_ARRAY_S(0);
    
for(double idx = 0; idx < ARRAY_SIZE(arrDate); idx = idx + 1)
{
    arrsDate[idx] = REAL_TO_DATE(arrDate[idx]);
    arrsTime[idx] = REAL_TO_TIME(arrTime[idx]);
}
        
    arrTime = arrTime / (24 * 60);
    arrDate = arrDate + arrTime;
    
    double nArraySize = ARRAY_SIZE(arrClose);
    
    // Note, that, technically, we will do better, 
    // if we use the old range for normalization
    double nExtractRecords = //26547;//
        0.7 * ARRAY_SIZE(arrClose);
    
    // -------
    array_s arrParameters = CREATE_ARRAY_S(0);
    array arrNeurons = CREATE_ARRAY(0);

    // NN: 1, Trades: 32(Buy: 32, Sell :0) 
    // NocInterval: 12, Range: 0.004, Ma: 3 Lag: 2, 
    // Neurons: 3 
    // Stop: 0.0200, Tp: 0.0000, Stop increase: 0.600000, 
    // Buy: 0.160, Sell: 0.900 
    // Drawdown: 0.915 Profit: 5757.000000 (long: 5757.000000, short: 0.000000) 
    arrParameters[0] = "12,0.004,3,2";
    arrNeurons[0] = 3;
    double dStopIncrease = 0.6;
    double dBuyLevel = 0.160;
        
    // NN: 2, Trades: 52(Buy: 52, Sell :0) 
    // NocInterval: 12, Range: 0.004, Ma: 3 Lag: 2, 
    // Neurons: 4 
    // Stop: 0.0200, Tp: 0.0000, Stop increase: 0.200000, 
    // Buy: 0.170, Sell: 0.900 
    // Drawdown: 0.702 Profit: 4869.000000 (long: 4869.000000, short: 0.000000) 
//    arrParameters[0] = "12,0.004,3,2";
//    arrNeurons[0] = 4;
//    double dStopIncrease = 0.2;
//    double dBuyLevel = 0.170;

    // NN: 3, Trades: 45(Buy: 45, Sell :0) 
    // NocInterval: 12, Range: 0.004, Ma: 3 Lag: 2, 
    // Neurons: 4 
    // Stop: 0.0200, Tp: 0.0000, Stop increase: 0.500000, 
    // Buy: 0.170, Sell: 0.900 
    // Drawdown: 0.732 Profit: 5165.000000 (long: 5165.000000, short: 0.000000) 
//    arrParameters[0] = "12,0.004,3,2";
//    arrNeurons[0] = 4;
//    double dStopIncrease = 0.5;
//    double dBuyLevel = 0.170;
    
    // NN: 4, Trades: 46(Buy: 46, Sell :0) 
    // NocInterval: 12, Range: 0.004, Ma: 3 Lag: 2, 
    // Neurons: 10 
    // Stop: 0.0200, Tp: 0.0000, Stop increase: 0.600000, 
    // Buy: 0.170, Sell: 0.900 
    // Drawdown: 0.864 Profit: 4147.000000 (long: 4147.000000, short: 0.000000) 
//    arrParameters[0] = "12,0.004,3,2";
//    arrNeurons[0] = 10;
//    double dStopIncrease = 0.6;
//    double dBuyLevel = 0.170;
    
            
    // ------------
    
    array arrLags = CREATE_ARRAY(0);
    array_s arrStrLags = CREATE_ARRAY_S(0);
    
    // ATTN: These lags are based on nWinLag. 
    // Example: nWinLag = 1, then each number should be increased by 1
    arrStrLags[0] = "17,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16";

    array arrStopLoss = CREATE_ARRAY(0);
    arrStopLoss[0] = 0.02;

    array arrNoc;
    array arrNocSmooth;

    double nRemoveFirst = 200; // arrOutLag[ARRAY_SIZE(arrOutLag) - 1];

    double dStopError = 0;
    double nStopEpoch = 5000; 

    array arrNn = CREATE_ARRAY(0);

    string strParam;
    string strToken;
    double nMa;
    double hNn;

    // ------

    double nNetNum = 0;
    double nCounter = 0;
    double dExpectedCycles = ARRAY_SIZE(arrParameters)
        * ARRAY_SIZE(arrStrLags) * ARRAY_SIZE(arrNeurons);

    string strResult = "";
    
    double dErrorMin = -1;

    double bSuccess;
    double nWinners = 0;
    double nTradeNumber;

    array arrBalance = CREATE_ARRAY(0);
    array arrBalanceBuy = CREATE_ARRAY(0);
    array arrBalanceSell = CREATE_ARRAY(0);

    string strXML = "<forex>\r\n";
    
    double nImageNum = 0;

    for(double nParIdx = 0; nParIdx < ARRAY_SIZE(arrParameters); 
        nParIdx = nParIdx + 1)
    {
        strParam = arrParameters[nParIdx];
        strToken = GET_TOKEN(strParam, ",");
        double nInterval = STR2NUM(strToken);

        strToken = GET_TOKEN(strParam, ",");

        double dRange = STR2NUM(strToken);

        arrNoc = CreateNoc(nInterval, dRange);

        strToken = GET_TOKEN(strParam, ",");
        nMa = STR2NUM(strToken);

        arrNocSmooth = EXP_MVAVG(arrNoc, nMa);

        strToken = GET_TOKEN(strParam, ",");
        double nOutLag = STR2NUM(strToken);
        
        for(double nLagIdx = 0; nLagIdx < ARRAY_SIZE(arrStrLags); 
            nLagIdx = nLagIdx + 1)
        {
            double nNumOfLags;
            string strLagBuf = arrStrLags[nLagIdx];
            CreateLagFile(strLagBuf, nRemoveFirst);

            for(double nNeuronsIdx = 0; nNeuronsIdx < ARRAY_SIZE(arrNeurons); 
                nNeuronsIdx = nNeuronsIdx + 1)
            {
                nCounter = nCounter + 1;

                double nNeurons = arrNeurons[nNeuronsIdx];
                        
                PRINT("%.0f", nCounter, " of %.0f\r\n", dExpectedCycles);

//                NewNn(arrLags, dStopError, nStopEpoch, nNeurons);
//                TeachNn();

                hNn = OPEN_NN(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), 
                    bIsPathRelative);

                APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrNocSmooth, arrLags, 1, arrNn);

                for(double nStopIdx = 0; nStopIdx < ARRAY_SIZE(arrStopLoss); 
                    nStopIdx = nStopIdx + 1)
                {
                    double dStopLoss = arrStopLoss[nStopIdx];
                    double dTakeProfit = 0;
                            
//                    double dStopIncrease = 0.5;    
//                    for(double dStopIncrease = 0; dStopIncrease < 0.7; 
//                        dStopIncrease = dStopIncrease + 0.1)
//                    {
//                        OUT_CLEANUP();

//                        for(double nBuyIdx = 0; nBuyIdx < 30; nBuyIdx = nBuyIdx + 1)
//                        {
                            nImageNum = nImageNum + 1;
                            
//                            double dBuyLevel = 0.190;//0.1 + 0.01 * nBuyIdx;

//                            for(double nSellIdx = 0; nSellIdx < 30; 
//                                nSellIdx = nSellIdx + 1)
//                            {
//                                double dSellLevel = 0.9 - 0.01 * nSellIdx;
                                double dSellLevel = 0.9;

                                PRINT("%.0f", nCounter, " of %.0f", dExpectedCycles);

                                Test();
                                                                        
                                if(bSuccess == 0)
                                {
                                    PRINT(" (%.0f)\r\n", nWinners);
//                                    continue nStopIdx;
                                }
    
                                nWinners = nWinners + 1;
                                PRINT(" (%.0f)\r\n", nWinners);    
                                                
                                Chart(strForexName);
//                            }
//                        }
//                    }
                }
                
                CLOSE_NN(hNn);
            }
        }
    }

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", "chart_forex_som", 
        "root", strXML);
    SHOW_XML(strImagePath + "chart_forex_som.xml");

    PRINT("%s\r\nDone\r\n", strResult);
}

// ---------------

void Test()
{
    bSuccess = 1;
    
    ARRAY_REMOVE(arrBalance, -1);
    arrBalance[0] = 1000;

    ARRAY_REMOVE(arrBalanceBuy, -1);
    arrBalanceBuy[0] = 0;

    ARRAY_REMOVE(arrBalanceSell, -1);
    arrBalanceSell[0] = 0;

    array arrBars = CREATE_ARRAY(0);
    arrBars[0] = 0;

    double dLotSize = 100;        // 0.1 lot
    double nType = 0;            // 1 buy, -1 sell, 0 - none

    double dSpread = 0.0005;
    double bStop;

    double dMaxDrawDown = 0;            // Max. Drawdown
    double dCurrentMax = 1000;        // Used to calculate drawdown

    nTradeNumber = 0;
    double nTradeNumberBuy = 0;
    double nTradeNumberSell = 0;
    double dOpenPrice;
    double dStop;
    double dTp = 0;

    for(double nBar = nRemoveFirst + 1; 
        nBar < ARRAY_SIZE(arrClose) - 1; nBar = nBar + 1)
    {
        if(nType != 0)
        {
            bStop = 0;
            double dClosedAt;

            // If BUY and stop loss or take profit reached
            if(nType == -1 && (arrLow[nBar] <= dStop || (dTakeProfit > 0 
                && arrHigh[nBar] >= dTp - dSpread)))
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1]; 

                bStop = 1;
                dClosedAt = arrLow[nBar + 1];
            }
            else
            {
                if(nType == 1 && (arrHigh[nBar] >= dStop - dSpread ||
                    (dTakeProfit > 0 && arrLow[nBar] <= dTp)))
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] = 
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    bStop = 1;
                    dClosedAt = arrHigh[nBar + 1];
                }
            }

            if(bStop == 1)
            {
                nType = 0;
                arrBars[ARRAY_SIZE(arrBars)] = nBar;
            }
        }

        double dDrawDown = (dCurrentMax - 
            arrBalance[ARRAY_SIZE(arrBalance) - 1]) / 1000; //dCurrentMax; 
        dMaxDrawDown = MAX(dMaxDrawDown, dDrawDown);
        dCurrentMax = MAX(dCurrentMax, arrBalance[ARRAY_SIZE(arrBalance) - 1]);

        if(dMaxDrawDown > 1)
        {
            bSuccess = 0;
            break nBar;
        }

        if(nType != -1 && arrNn[nBar - 1] <= dBuyLevel && arrNn[nBar] >= dBuyLevel)
        {
            if(nType == 1)
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                arrBars[ARRAY_SIZE(arrBars)] = nBar;
                
                nType = 0;
            }

            dOpenPrice = arrHigh[nBar + 1];
            dStop = dOpenPrice - dStopLoss;
            
            dTp = dOpenPrice + dTakeProfit;
            nType = -1;
            
            nTradeNumber = nTradeNumber + 1;
            nTradeNumberBuy = nTradeNumberBuy + 1;
        }
        else
        {
            if(nType != 1 && arrNn[nBar - 1] >= dSellLevel && arrNn[nBar] <= dSellLevel)
            {
                if(nType == -1)
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] =
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] =
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] =
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];

                    arrBars[ARRAY_SIZE(arrBars)] = nBar;
                
                    nType = 0;
                }
            }
        }

        if(dStopIncrease != 0)
        {
            if(nType == -1)
            {
                if(arrLow[nBar] - dStop >= dStopLoss * (1 + dStopIncrease))
                {
                    dStop = arrLow[nBar] - dStopLoss;
                }
            }
            else
            {
                if(nType == 1)
                {
                    if(dStop - arrHigh[nBar] >= dStopLoss * (1 + dStopIncrease))
                    {
                        dStop = arrHigh[nBar] + dStopLoss;
                    }
                }
            }
        }
    }

    // If at the end we have open positions, close them

    if(nType == 1)
    {
        arrBalance[ARRAY_SIZE(arrBalance)] = 
            arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

        arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;
    }
    else
    {
        if(nType == -1)
        {
            arrBalance[ARRAY_SIZE(arrBalance)] = 
                arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];
    
            arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;
            
        }
    }

    if(ARRAY_SIZE(arrBalance) <= 1 || 
        (arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] <= 500 
            && arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] <= 200) ||
        nTradeNumber < 20)
    {
        bSuccess = 0;
    }
}

// ---------------

array CreateNoc(double nInterval, double dMinRange)
{    
    PRINT("%s\r\n", "Creating NOC indicator");

    array arrNormOnCondition = CREATE_ARRAY(0);
    array arrPeriodLow = CREATE_ARRAY(0);
    array arrPeriodHigh = CREATE_ARRAY(0);

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MV_MIN(arrLow, nInterval);
    array arrPeriodHigh = MV_MAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrNormOnCondition[i] = 0;
    }
    
    double dClose;
    double dLow;
    double dHigh;
    for(i = nInterval; i < nArraySize; i = i + 1)
    {
        dClose = arrClose[i-1];
        dLow = arrPeriodLow[i-1];
        dHigh = arrPeriodHigh[i-1];

        if(dHigh - dLow > dMinRange)
        {
            // / 2 + 1 to confine to 0...1 instead of -1...1
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / (dHigh - dLow)) / 2 + 0.5; 
        }
        else
        {
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / dMinRange) / 2 + 0.5; 
        }
    }

    return arrNormOnCondition;
}

// ----------------

void CreateLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strLagFileName, "wb", bIsPathRelative);

    F_PRINT(hFile, "%s", "No,Noc");

    ARRAY_REMOVE(arrLags, -1);

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrLags[i] = STR2NUM(strToken) + nOutLag;
    }

    double nFullLag;
    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        nFullLag = arrLags[i];
        F_PRINT(hFile, ",NocMa%.0f", nMa, "-%.0f", nFullLag); 
    }

    F_PRINT(hFile, "%s\r\n", "");

    double nNum;
    for(i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        nNum = i - nRemoveFirst + 1;
        F_PRINT(hFile, "%.0f", nNum, ",%f", arrNocSmooth[i]);     

        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrNocSmooth[i - arrLags[j]]);     
        }

        F_PRINT(hFile, "%s\r\n", "");
    }

    F_CLOSE(hFile);
}

// --------------

void NewNn(array arrLags, double dStopError, double nStopEpoch, 
    double nNeuronsLayer2)
{
    double nSkipBefore = 0;
    double nSkipAfter = 0;
    string strStartLine = "";
    string strEndLine = "";
    double bReverseArrays = 0;

    // Inputs
    array arrInputColumns = CREATE_ARRAY(0);
    array_s arrInputColumnNames = CREATE_ARRAY_S(0);
    
    // 0  - Number, 1 - Noc, our input begins at column No 2
    for(double nInputCol = 0; nInputCol < ARRAY_SIZE(arrLags); 
        nInputCol = nInputCol + 1) 
    {
        arrInputColumns[nInputCol] = nInputCol + 2;
        arrInputColumnNames[nInputCol] = 
            "Clv-" + NUM2STR(arrLags[nInputCol], "%.0f"); 
    }

    array arrOutputColumns = CREATE_ARRAY(0);
    arrOutputColumns[0] = 1;    // Noc

    array_s arrOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutputColumnNames[0] = "Noc";

    double nNeuronsLayer1 = ARRAY_SIZE(arrLags);
//    double nNeuronsLayer2 = 7;
    double nNeuronsLayer3 = 1;
    double nNeuronsLayer4 = 0;
    double nLayers = 3; 
    double nActivation = 0;
    double nAdjustRange = 1.0;

    array arrOutTabInputColumns = CREATE_ARRAY(0);
    arrOutTabInputColumns[0] = 0;    // Number

    array_s arrOutTabInputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabInputColumnNames[0] = "No";

    array arrOutTabOutputColumns = CREATE_ARRAY(0);
    // Desired output and NN output will be added to the 
    // same list, right after inputs
    arrOutTabOutputColumns[0] = ARRAY_SIZE(arrLags) + 2;    
    arrOutTabOutputColumns[1] = ARRAY_SIZE(arrLags) + 3;

    array_s arrOutTabOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabOutputColumnNames[0] = "Noc"; 
    arrOutTabOutputColumnNames[1] = "NN: Noc" ;

    CREATE_NN(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), bIsPathRelative, 
        strLagFileName, bIsPathRelative, nSkipBefore, nSkipAfter, 
        strStartLine, strEndLine, bReverseArrays, arrInputColumns, 
        arrInputColumnNames, arrOutputColumns, arrOutputColumnNames, 
        nExtractRecords, dStopError, nStopEpoch, nNeuronsLayer1, 
        nNeuronsLayer2, nNeuronsLayer3, nNeuronsLayer4, nLayers, 
        nActivation, nAdjustRange, arrOutTabInputColumns, arrOutTabInputColumnNames,
        arrOutTabOutputColumns, arrOutTabOutputColumnNames);
}

// ----------------

void TeachNn()
{
    PRINT("%s\r\n", "Opening NN dialog, teaching the NN");

    double bStartLearning = 1; 
    double bResumeScript = 1;
    double bReset = 1;

    OPEN_NN_FILE(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), 
        bIsPathRelative, bStartLearning, bResumeScript, bReset);
}


// -------------

void Chart(string strForexName)
{
    string strWinNnFileName;
        
    string strImageFileName = strImagePath + strForexName + 
        NUM2STR(nImageNum, "_%.0f") + "_nn_06.png";    
            
    strXML = strXML + "\t<symbol>\r\n\t\t<symbol>\r\n";

    strXML = strXML + "NN: " + NUM2STR(nCounter, "%.0f, ")
        + "Trades: " + NUM2STR(nTradeNumber, "%.0f")
        + "(Buy: " + NUM2STR(nTradeNumberBuy, "%.0f")
        + ", Sell :" + NUM2STR(nTradeNumberSell, "%.0f)\r\n")
        + "NocInterval: " + NUM2STR(nInterval, "%.0f")
        + ", Range: " + NUM2STR(dRange, "%.3f")
        + ", Ma: " + NUM2STR(nMa, "%.0f\r\n")

        + "Lag: " + NUM2STR(nOutLag, "%.0f")
        + ", Neurons: " + NUM2STR(nNeurons, "%.0f\r\n")
        + "Stop: " + NUM2STR(dStopLoss, "%.4f")
        + ", Tp: " + NUM2STR(dTakeProfit, "%.4f")
        + NUM2STR(dStopIncrease, ", Stop increase: %f")
        + ", Buy: " + NUM2STR(dBuyLevel, "%.3f")
        + ", Sell: " + NUM2STR(dSellLevel, "%.3f\r\n")

        + "Drawdown: " + NUM2STR(dMaxDrawDown, "%.3f\r\n")

        + "Profit: " + NUM2STR(arrBalance[ARRAY_SIZE(arrBalance) - 1] - 1000, "%f")
        + " (long: " + NUM2STR(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1], "%f")
        + ", short: " + NUM2STR(arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1], "%f)\r\n");

    strXML = strXML + "\t\t</symbol>\r\n";

    strXML = strXML + "\t\t" + SAVE_CHART(400, 300, 0, strImageFileName, 
        arrBars, arrBalance, arrBalanceBuy, arrBalanceSell);
    strXML = strXML + "\t</symbol>\r\n";
}

Exporting weights of the Neural Networks

The next step is to export weights of the Neural Network to the scripting language of a trading platform of our choice. Here we are just repeating the Neural Networks Forex Trading. To do it, we add couple of PRINT statements to the som_06.tsc, and it will produce the necessary output. Simply insert the following code in the som_06.tsc:

som_08.tsc, fragment

hNn = OPEN_NN(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), bIsPathRelative);

APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrNocSmooth, arrLags, 1, arrNn);
                
// ------ Getting NN info
                                
array arrDescription = GET_NN_DESCRIPTION(hNn);
                
PRINT("\r\nActivation Type: %.0f\r\n", arrDescription[5]);

double nLayers = arrDescription[0];

array arrNumOfNeurons = CREATE_ARRAY(0);
for(double nLayer = 0; nLayer < nLayers; nLayer = nLayer + 1)
{
    arrNumOfNeurons[nLayer] = arrDescription[1 + nLayer];
}

for(nLayer = 0; nLayer < nLayers; nLayer = nLayer + 1)
{
    PRINT("\r\n\t// Layer: %.0f\r\n", nLayer);

    double nNumOfInputs = arrNumOfNeurons[nLayer] + 1;
    if(nLayer != 0)
    {
        nNumOfInputs = arrNumOfNeurons[nLayer - 1] + 1;
    }

    array arrWeights = GET_NN_WEIGHTS(hNn, nLayer);

    double nWeight = 0;
    for(double nNeuron = 0; nNeuron < arrNumOfNeurons[nLayer]; 
        nNeuron = nNeuron + 1)
    {
        for(double nNeuronWeight = 0; nNeuronWeight < nNumOfInputs; 
            nNeuronWeight = nNeuronWeight + 1) 
        {
            if(nLayer == 0)
            {
                PRINT("\tarrWeights_0[%.0f] = ", nWeight,
                    "%f;\r\n", arrWeights[nWeight]);
            }
            else 
            {
                if(nLayer == 1)
                {
                    PRINT("\tarrWeights_1[%.0f] = ", nWeight,
                        "%f;\r\n", arrWeights[nWeight]);
                }
                else
                {
                    PRINT("\tarrWeights_2[%.0f] = ", nWeight,
                        "%f;\r\n", arrWeights[nWeight]);
                }
            }
                        
            nWeight = nWeight + 1;
        }
    }
}

// ------                

Note, that the logic behind it was discussed in Neural Networks Forex Trading article already. Briefly, the output of this script is formated to be compatible with the MQ4, MetaTrader's scripting engine. MetaTrader is a trading platform we use, if you want something different, like TradeStation, for example, you will have to alter the code to comply to its syntax.

Then, in the following chapters, we are going to insert this code in the MetaTrader's indicator, and to use it to trade.

Emulating APPLY_NN

The next step is not really required, but it is something, that may be useful to find and fix bugs in code. We are going to create a version of som_06.tsc, but this time, we will use SLANG (Cortex built-in scripting language) to emulate APPLY_NN function. The reason is, in the next chapter we are going to port it to the scripting language of a MetaTrader trading platform, so it is a good idea to make sure everything works.

som_09.tsc

void main()
{
    OUT_CLEANUP();

    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    PRINT("%s\r\n", "Deleting image files...");

    array_s arrDirList = GET_DIR(strImagePath, 0, "*.png");
    for(double n = 0; n < ARRAY_SIZE(arrDirList); n = n + 1)
    {
        F_UNLINK(arrDirList[n]);
    }

    string strForexName = "EURUSD_H1";

    // ------------

    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\" 
            + strForexName + ".TXT";
    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\"
            + strForexName + "_nn_09.lgg";
    
    double bIsPathRelative = 1;

    array arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    TABLE_LOADER(strDataFileName, bIsPathRelative, 0, "", 0, "", 0, 
        arrDate, 1, arrTime, 2, arrOpen, 3, arrHigh, 
        4, arrLow, 5, arrClose);
    arrTime = arrTime / (24 * 60);
    arrDate = arrDate + arrTime;
    
    double nArraySize = ARRAY_SIZE(arrClose);
    
    // Note, that, technically, we will do better, 
    // if we use the old range for normalization
    double nExtractRecords = //26547;//
        0.7 * ARRAY_SIZE(arrClose);
    
    // -------
    array_s arrParameters = CREATE_ARRAY_S(0);
    array arrNeurons = CREATE_ARRAY(0);

    

    // NN: 1, Trades: 32(Buy: 32, Sell :0) 
    // NocInterval: 12, Range: 0.004, Ma: 3 Lag: 2, 
    // Neurons: 3 
    // Stop: 0.0200, Tp: 0.0000, Stop increase: 0.600000, 
    // Buy: 0.160, Sell: 0.900 
    // Drawdown: 0.915 Profit: 5757.000000 (long: 5757.000000, short: 0.000000) 
    arrParameters[0] = "12,0.004,3,2";
    arrNeurons[0] = 3;
    double dStopIncrease = 0.6;
    double dBuyLevel = 0.160;
        
    // ------------
    
    array arrLags = CREATE_ARRAY(0);
    array_s arrStrLags = CREATE_ARRAY_S(0);
    
    // ATTN: These lags are based on nWinLag. 
    // Example: nWinLag = 1, then each number should be increased by 1
    arrStrLags[0] = "17,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16";

    array arrStopLoss = CREATE_ARRAY(0);
    arrStopLoss[0] = 0.02;

    array arrNoc;
    array arrNocSmooth;

    double nRemoveFirst = 200; // arrOutLag[ARRAY_SIZE(arrOutLag) - 1];

    double dStopError = 0;
    double nStopEpoch = 5000; 

    array arrNn = CREATE_ARRAY(0);

    string strParam;
    string strToken;
    double nMa;
    double hNn;

    // ------

    double nNetNum = 0;
    double nCounter = 0;
    double dExpectedCycles = ARRAY_SIZE(arrParameters)
        * ARRAY_SIZE(arrStrLags) * ARRAY_SIZE(arrNeurons);

    string strResult = "";
    
    double dErrorMin = -1;

    double bSuccess;
    double nWinners = 0;
    double nTradeNumber;

    array arrBalance = CREATE_ARRAY(0);
    array arrBalanceBuy = CREATE_ARRAY(0);
    array arrBalanceSell = CREATE_ARRAY(0);

    string strXML = "<forex>\r\n";
    
    double nImageNum = 0;

    for(double nParIdx = 0; nParIdx < ARRAY_SIZE(arrParameters); 
        nParIdx = nParIdx + 1)
    {
        strParam = arrParameters[nParIdx];
        strToken = GET_TOKEN(strParam, ",");
        double nInterval = STR2NUM(strToken);

        strToken = GET_TOKEN(strParam, ",");

        double dRange = STR2NUM(strToken);

        arrNoc = CreateNoc(nInterval, dRange);

        strToken = GET_TOKEN(strParam, ",");
        nMa = STR2NUM(strToken);

        arrNocSmooth = EXP_MVAVG(arrNoc, nMa);

        strToken = GET_TOKEN(strParam, ",");
        double nOutLag = STR2NUM(strToken);
        
        for(double nLagIdx = 0; nLagIdx < ARRAY_SIZE(arrStrLags); 
            nLagIdx = nLagIdx + 1)
        {
            double nNumOfLags;
            string strLagBuf = arrStrLags[nLagIdx];
            CreateLagFile(strLagBuf, nRemoveFirst);

            for(double nNeuronsIdx = 0; nNeuronsIdx < ARRAY_SIZE(arrNeurons); 
                nNeuronsIdx = nNeuronsIdx + 1)
            {
                nCounter = nCounter + 1;

                double nNeurons = arrNeurons[nNeuronsIdx];
                        
                PRINT("%.0f", nCounter, " of %.0f\r\n", dExpectedCycles);

                ApplyScriptNn();

                for(double nStopIdx = 0; nStopIdx < ARRAY_SIZE(arrStopLoss); 
                    nStopIdx = nStopIdx + 1)
                {
                    double dStopLoss = arrStopLoss[nStopIdx];
                    double dTakeProfit = 0;
                            
                    nImageNum = nImageNum + 1;
                            
                    double dSellLevel = 0.9;

                    PRINT("%.0f", nCounter, " of %.0f", dExpectedCycles);

                    Test();
                                                                        
                    if(bSuccess == 0)
                    {
                        PRINT(" (%.0f)\r\n", nWinners);
                    }
    
                    nWinners = nWinners + 1;
                    PRINT(" (%.0f)\r\n", nWinners);    
                                                
                    Chart(strForexName);

                }
            }
        }
    }

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", "chart_forex_som", 
        "root", strXML);
    SHOW_XML(strImagePath + "chart_forex_som.xml");

    PRINT("%s\r\nDone\r\n", strResult);
}

// ---------------

void Test()
{
    bSuccess = 1;
    
    ARRAY_REMOVE(arrBalance, -1);
    arrBalance[0] = 1000;

    ARRAY_REMOVE(arrBalanceBuy, -1);
    arrBalanceBuy[0] = 0;

    ARRAY_REMOVE(arrBalanceSell, -1);
    arrBalanceSell[0] = 0;

    array arrBars = CREATE_ARRAY(0);
    arrBars[0] = 0;

    double dLotSize = 100;        // 0.1 lot
    double nType = 0;            // 1 buy, -1 sell, 0 - none

    double dSpread = 0.0005;
    double bStop;

    double dMaxDrawDown = 0;            // Max. Drawdown
    double dCurrentMax = 1000;        // Used to calculate drawdown

    nTradeNumber = 0;
    double nTradeNumberBuy = 0;
    double nTradeNumberSell = 0;
    double dOpenPrice;
    double dStop;
    double dTp = 0;

    for(double nBar = nRemoveFirst + 1; 
        nBar < ARRAY_SIZE(arrClose) - 1; nBar = nBar + 1)
    {
        if(nType != 0)
        {
            bStop = 0;
            double dClosedAt;

            // If BUY and stop loss or take profit reached
            if(nType == -1 && (arrLow[nBar] <= dStop || (dTakeProfit > 0 
                && arrHigh[nBar] >= dTp - dSpread)))
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1]; 

                bStop = 1;
                dClosedAt = arrLow[nBar + 1];
            }
            else
            {
                if(nType == 1 && (arrHigh[nBar] >= dStop - dSpread ||
                    (dTakeProfit > 0 && arrLow[nBar] <= dTp)))
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] = 
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    bStop = 1;
                    dClosedAt = arrHigh[nBar + 1];
                }
            }

            if(bStop == 1)
            {
                nType = 0;
                arrBars[ARRAY_SIZE(arrBars)] = nBar;
            }
        }

        double dDrawDown = (dCurrentMax - 
            arrBalance[ARRAY_SIZE(arrBalance) - 1]) / 1000; //dCurrentMax; 
        dMaxDrawDown = MAX(dMaxDrawDown, dDrawDown);
        dCurrentMax = MAX(dCurrentMax, arrBalance[ARRAY_SIZE(arrBalance) - 1]);

        if(dMaxDrawDown > 1)
        {
            bSuccess = 0;
            break nBar;
        }

        if(nType != -1 && arrNn[nBar - 1] <= dBuyLevel && arrNn[nBar] >= dBuyLevel)
        {
            if(nType == 1)
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                arrBars[ARRAY_SIZE(arrBars)] = nBar;

                nType = 0;
            }

            dOpenPrice = arrHigh[nBar + 1];
            dStop = dOpenPrice - dStopLoss;
            dTp = dOpenPrice + dTakeProfit;
            nType = -1;
            
            nTradeNumber = nTradeNumber + 1;
            nTradeNumberBuy = nTradeNumberBuy + 1;
        }
        else
        {
            if(nType != 1 && arrNn[nBar - 1] >= dSellLevel && arrNn[nBar] <= dSellLevel)
            {
                if(nType == -1)
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] =
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] =
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] =
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];

                    arrBars[ARRAY_SIZE(arrBars)] = nBar;
                
                    nType = 0;
                }
            }
        }

        if(dStopIncrease != 0)
        {
            if(nType == -1)
            {
                if(arrLow[nBar] - dStop >= dStopLoss * (1 + dStopIncrease))
                {
                    dStop = arrLow[nBar] - dStopLoss;
                }
            }
            else
            {
                if(nType == 1)
                {
                    if(dStop - arrHigh[nBar] >= dStopLoss * (1 + dStopIncrease))
                    {
                        dStop = arrHigh[nBar] + dStopLoss;
                    }
                }
            }
        }
    }

    // If at the end we have open positions, close them

    if(nType == 1)
    {
        arrBalance[ARRAY_SIZE(arrBalance)] = 
            arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

        arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;
    }
    else
    {
        if(nType == -1)
        {
            arrBalance[ARRAY_SIZE(arrBalance)] = 
                arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];
    
            arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;

        }
    }

    if(ARRAY_SIZE(arrBalance) <= 1 || 
        (arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] <= 500 
            && arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] <= 200) ||
        nTradeNumber < 20)
    {
        bSuccess = 0;
    }
}

// ---------------

array CreateNoc(double nInterval, double dMinRange)
{    
    PRINT("%s\r\n", "Creating NOC indicator");

    array arrNormOnCondition = CREATE_ARRAY(0);
    array arrPeriodLow = CREATE_ARRAY(0);
    array arrPeriodHigh = CREATE_ARRAY(0);

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MV_MIN(arrLow, nInterval);
    array arrPeriodHigh = MV_MAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrNormOnCondition[i] = 0;
    }
    
    double dClose;
    double dLow;
    double dHigh;
    for(i = nInterval; i < nArraySize; i = i + 1)
    {
        dClose = arrClose[i];
        dLow = arrPeriodLow[i];
        dHigh = arrPeriodHigh[i];

        if(dHigh - dLow > dMinRange)
        {
            // / 2 + 1 to confine to 0...1 instead of -1...1
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / (dHigh - dLow)) / 2 + 0.5; 
        }
        else
        {
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / dMinRange) / 2 + 0.5; 
        }
    }

    return arrNormOnCondition;
}

// ----------------

void CreateLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strLagFileName, "wb", bIsPathRelative);

    F_PRINT(hFile, "%s", "No,Noc");

    ARRAY_REMOVE(arrLags, -1);

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrLags[i] = STR2NUM(strToken) + nOutLag;
    }

    double nFullLag;
    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        nFullLag = arrLags[i];
        F_PRINT(hFile, ",NocMa%.0f", nMa, "-%.0f", nFullLag); 
    }

    F_PRINT(hFile, "%s\r\n", "");

    double nNum;
    for(i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        nNum = i - nRemoveFirst + 1;
        F_PRINT(hFile, "%.0f", nNum, ",%f", arrNocSmooth[i]);     

        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrNocSmooth[i - arrLags[j]]);     
        }

        F_PRINT(hFile, "%s\r\n", "");
    }

    F_CLOSE(hFile);
}

// --------------

void ApplyScriptNn()
{
    // We know the structure, as we created this NN

    double nActivationType = 0;

    double nLayers = 3;

    array arrNeuronsInLayer = CREATE_ARRAY(0);
    arrNeuronsInLayer[0] = 17;
    arrNeuronsInLayer[1] = 3;
    arrNeuronsInLayer[2] = 1;

    array arrWeights_0 = CREATE_ARRAY(0);
    array arrWeights_1 = CREATE_ARRAY(0);
    array arrWeights_2 = CREATE_ARRAY(0);

    // Below, is the output of the forex_nn_05a, pasted into
    // our script (weight, exported from the NN we created and teached). 

    // Layer: 0
    arrWeights_0[0] = 877.529097;
    arrWeights_0[1] = -979.236938;
    arrWeights_0[2] = 397.873154;
    ...
    arrWeights_0[303] = -66.428302;
    arrWeights_0[304] = 35.207516;
    arrWeights_0[305] = 339.042214;

    // Layer: 1
    arrWeights_1[0] = 6.018999;
    arrWeights_1[1] = 5.440863;
    arrWeights_1[2] = 8.927424;
    ...
    arrWeights_1[51] = -31.510057;
    arrWeights_1[52] = 7.666437;
    arrWeights_1[53] = 58.141611;

    // Layer: 2
    arrWeights_2[0] = 5.346615;
    arrWeights_2[1] = 5.275201;
    arrWeights_2[2] = 6.543355;
    arrWeights_2[3] = 8.811740;

    // First and second layers produce one outpur per neuron
    // Third layer has one neuron and produces one output

    array arrOutput_0 = CREATE_ARRAY(0);
    array arrOutput_1 = CREATE_ARRAY(0);
    double dNnOutput;

    // ------

    // We will read patterns from the lag file, one line is one pattern
    array arrPattern = CREATE_ARRAY(0);
    
    double hFile = F_OPEN(strLagFileName, "rb", 1);
        
    // Skip the first line containing headers
    string strPattern = F_GETS(hFile); 

    // We do not need to normalize the data, as NOC is already 
    // normalized to 0-1 range

    string strPattern;
    string strToken;
    
    for(double nPattern = 0; nPattern < 100000; nPattern = nPattern + 1)
    {
        if(F_EOF(hFile) == 1)
        {
            break nPattern;
        }

        strPattern = F_GETS(hFile); 
        
        // The last CR-LF, empty string
        if(STR_LEN(strPattern) < 10)
        {
            break nPattern;
        }
        
        if(nPattern % 100 == 0)
        {
            PRINT("Pattern: %.0f\r\n", nPattern);
        }

        // Skip first two columns containing column number 
        // and desirable output
        strToken = GET_TOKEN(strPattern, ",");
        strToken = GET_TOKEN(strPattern, ",");

        ARRAY_REMOVE(arrPattern, -1);

        for(double nLag = 0; nLag < nNumOfLags; nLag = nLag + 1)
        {
            strToken = GET_TOKEN(strPattern, ",");
            double dValue = STR2NUM(strToken);
            arrPattern[nLag] = dValue;
        }

        // Pattern loaded, now process

        ARRAY_REMOVE(arrOutput_0, -1);
        ARRAY_REMOVE(arrOutput_1, -1);
        
        // The following code implements 3 C++ functions (together),
        // quoted above

        for(double nLayer = 0; nLayer < nLayers; nLayer = nLayer + 1)
        {
            // Absolute index of the weight in an array
            double nWeightIdx = 0;

            for(double nNeuron = 0; nNeuron < arrNeuronsInLayer[nLayer]; 
                nNeuron = nNeuron + 1)
            {
                double dLinearCombiner = 0;
    
                double nInputs = arrNeuronsInLayer[nLayer];
                if(nLayer != 0)
                {
                    nInputs = arrNeuronsInLayer[nLayer - 1];
                }

                for(double nInput = 0; nInput < nInputs; nInput = nInput + 1)
                {
                    double dInput;
                    double dWeight;
                    if(nLayer == 0)
                    {
                        dInput = arrPattern[nInput];
                        dWeight = arrWeights_0[nWeightIdx];
                    }
                    else
                    {
                        if(nLayer == 1)
                        {
                            dInput = arrOutput_0[nInput];
                            dWeight = arrWeights_1[nWeightIdx];
                        }
                        else
                        {
                            dInput = arrOutput_1[nInput];
                            dWeight = arrWeights_2[nWeightIdx];
                        }
                    }

                    dLinearCombiner = dLinearCombiner + dWeight * dInput;

                    nWeightIdx = nWeightIdx + 1;
                }        

                if(nLayer == 0)
                {
                    dWeight = arrWeights_0[nWeightIdx];
                }
                else
                {
                    if(nLayer == 1)
                    {
                        dWeight = arrWeights_1[nWeightIdx];
                    }
                    else
                    {
                        dWeight = arrWeights_2[nWeightIdx];
                    }
                }

                dLinearCombiner = dLinearCombiner + (-1) * dWeight;
                nWeightIdx = nWeightIdx + 1;

                double dActivation = 
                    Activation(dLinearCombiner / (nInput + 1), nActivationType);

                if(nLayer == 0)
                {
                    arrOutput_0[nNeuron] = dActivation;
                }
                else 
                {
                    if(nLayer == 1)
                    {
                        arrOutput_1[nNeuron] = dActivation;
                    }
                    else
                    {
                        // 1 neuron in the last layer
                        dNnOutput = dActivation;    
                    }
                }
            
            }    // for all neurons

        }    // for all layers

        arrNn[nPattern] = dNnOutput;
    
    }    // for all patterns

    double dInsert = arrNn[0];

    for(nPattern = 0; nPattern < nRemoveFirst; nPattern = nPattern + 1)
    {
        ARRAY_INSERT(arrNn, dInsert, 0);
    }
}

// -------------

double Activation(double u, double nType)
{
    double dEx = TANH(u / 2);
    if(dEx == 1 || dEx == -1)
    {
        dEx = dEx * 0.999999999;
    }
    
    // 0 - sigmoid, 1 - tangent
    if(nType == 0)
    {
        return (dEx + 1) / 2;
    }

    return dEx;
}

// ------

void Chart(string strForexName)
{
    string strWinNnFileName;
        
    string strImageFileName = strImagePath + strForexName + 
        NUM2STR(nImageNum, "_%.0f") + "_nn_09.png";    
            
    strXML = strXML + "\t<symbol>\r\n\t\t<symbol>\r\n";

    strXML = strXML + "NN: " + NUM2STR(nCounter, "%.0f, ")
        + "Trades: " + NUM2STR(nTradeNumber, "%.0f")
        + "(Buy: " + NUM2STR(nTradeNumberBuy, "%.0f")
        + ", Sell :" + NUM2STR(nTradeNumberSell, "%.0f)\r\n")
        + "NocInterval: " + NUM2STR(nInterval, "%.0f")
        + ", Range: " + NUM2STR(dRange, "%.3f")
        + ", Ma: " + NUM2STR(nMa, "%.0f\r\n")

        + "Lag: " + NUM2STR(nOutLag, "%.0f")
        + ", Neurons: " + NUM2STR(nNeurons, "%.0f\r\n")
        + "Stop: " + NUM2STR(dStopLoss, "%.4f")
        + ", Tp: " + NUM2STR(dTakeProfit, "%.4f")
        + NUM2STR(dStopIncrease, ", Stop increase: %f")
        + ", Buy: " + NUM2STR(dBuyLevel, "%.3f")
        + ", Sell: " + NUM2STR(dSellLevel, "%.3f\r\n")

        + "Drawdown: " + NUM2STR(dMaxDrawDown, "%.3f\r\n")

        + "Profit: " + NUM2STR(arrBalance[ARRAY_SIZE(arrBalance) - 1] - 1000, "%f")
        + " (long: " + NUM2STR(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1], "%f")
        + ", short: " + 
            NUM2STR(arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1], "%f)\r\n");

    strXML = strXML + "\t\t</symbol>\r\n";

    strXML = strXML + "\t\t" + SAVE_CHART(400, 300, 0, strImageFileName, 
        arrBars, arrBalance, arrBalanceBuy, arrBalanceSell);

    strXML = strXML + "\t</symbol>\r\n";
}

Porting FFBP Neural Network to trading platform

The following code is taken from Neural Networks Forex Trading article. It is a ready to be used pair of indicator and an expert, that uses that indicator. No kohonen neural network code yet. As usual, please keep in mind, that it is just an example, that is not necessarily going to always be profitable.

Also, as some of our MetaTrader code is the same for all experts and indicators, we moved it to a separate library file. MetaTrader's libraries are nothing but includable files. This library takes care of synhronization, when two or more expert are trying to run in the same time, as well as of few other things. If you use MetaTrader, it will help you to create robust experts, in any case, the MQL language is easy to understand.

mylib.mql, a helper library

double dTp = 0;
datetime timePrev = 0;
int nBars;
int nDelaySeconds = 10;

int nSlip = 5;

double dProfit = 0;
double dInitAmount = 1000;
double dLotSize = 0.1;

int nMagic = 0;
bool bReportDone = false;

string strTradeSemaphore = "TradeSemaphore";

// ------

double GetLotSize(double dCustomCoef = 1, double dInitFraction = 0.1, 
    double dProfitFraction = 0.1)
{
    double dLot = 0.1 * dCustomCoef;
    
    if(bUseMm)
    {
        dLot  = (dInitFraction * dInitAmount + dProfitFraction * dProfit) / 1000;
    }

    dLot = MathFloor(dLot * 10) / 10;
    
    if(dLot < 0.1)
        dLot = 0.1;
    
    return(dLot);
}

// ------

int Sell(string strExpertName, double dLot = 0, int nMagicNumber = -1)
{
    int nResult = -1;
    
    if(nMagicNumber == -1)
        nMagicNumber = nMagic;

    // This code is for trade contest. After the contest, leave the commented code
    // dLotSize = GetLotSize();
    if(dLot == 0)
        dLotSize = GetLotSize();
    else
        dLotSize = dLot * GetLotSize();
    
    if(AccountFreeMargin() < dLotSize * dInitAmount || AccountFreeMargin() < 500)
        return(nResult);

    double dTp;
    if(dTakeProfit == 0)
        dTp = 0;
    else
        dTp = Bid - dTakeProfit;

    for(int nTry = 0; nTry < 10; nTry++)
    {
        SaveComment("\r\n" + Day() + "." + Month() + "." + Year() + " " 
            + Hour() + ":" + Minute() + ":" + Seconds());
        SaveComment(" Trying to sell, attempt " + nTry + "\r\n");
        SaveComment("\r\nAsk: " + Ask + ", StopLoss: " + dStopLoss + 
            ", TakeProfit: " + dTakeProfit + "\r\n");
        
        nResult = OrderSend(Symbol(), OP_SELL, dLotSize, Bid, nSlip, Bid + dStopLoss, 
            dTp, strExpertName, nMagicNumber, 0, OrangeRed);
        
        Sleep(10000);
        if(nResult != -1)
        {
            SaveComment(" successfull\r\n");
            break;
        }
        else
        {
            SaveComment(" failed, error " + GetLastError() + "\r\n");
            RefreshRates();
        }
    }    
    
    if(nResult == -1)
    {
        int nError = GetLastError();
        Alert(strExpertName + " sell error: " + nError + "\r\n" +
            Bid + ", " + dStopLoss + ", " + dTp);
        SaveComment(strExpertName + " sell error: " + nError + "\r\n" +
            Bid + ", " + dStopLoss + ", " + dTp);
    }
    
    return(nResult);
}

// ------

int Buy(string strExpertName, double dLot = 0, int nMagicNumber = -1)
{
    int nResult = -1;
    
    if(nMagicNumber == -1)
        nMagicNumber = nMagic;

    // This code is for trade contest. After the contest, leave the commented code
    // dLotSize = GetLotSize();
    
    dLotSize = GetLotSize(dLot);
    
    if(AccountFreeMargin() < dLotSize * dInitAmount || AccountFreeMargin() < 500)
        return(nResult);

    double dTp;
    if(dTakeProfit == 0)
        dTp = 0;
    else
        dTp = Ask + dTakeProfit;

    for(int nTry = 0; nTry < 10; nTry++)
    {
        SaveComment("\r\n" + Day() + "." + Month() + "." + Year() + " " 
            + Hour() + ":" + Minute() + ":" + Seconds());
        SaveComment(" Trying to buy, attempt " + nTry + "\r\n");
        SaveComment("\r\nBid: " + Bid + ", StopLoss: " 
            + dStopLoss + ", TakeProfit: " + dTakeProfit);
        
        nResult = OrderSend(Symbol(), OP_BUY, dLotSize, Ask, nSlip, 
            Ask - dStopLoss, dTp, strExpertName, nMagicNumber, 0, Aqua);

        Sleep(10000);
        if(nResult != -1)
        {
            SaveComment(" successfull\r\n");
            break;
        }
        else
        {
            SaveComment(" failed, error " + GetLastError() + "\r\n");
            RefreshRates();
        }
    }    

    if(nResult == -1)
    {
        int nError = GetLastError();
        Alert(strExpertName + " buy error: " + nError + "\r\n" +
            Ask + ", " + dStopLoss + ", " + dTp);

        SaveComment(strExpertName + " buy error: " + nError + "\r\n" +
            Ask + ", " + dStopLoss + ", " + dTp);
    }

    return(nResult);
}

// ------

void ModifyOrders()
{
    if(dTrailingStop == 0)
        return;

    for(int nCnt = 0; nCnt < OrdersTotal(); nCnt++)
    {
        OrderSelect(nCnt, SELECT_BY_POS, MODE_TRADES);
        if(OrderMagicNumber() == nMagic)
        {
            if(OrderType() == OP_BUY)
            {
                if(OrderStopLoss() < Bid - dTrailingStop)
                {
                    OrderModify(OrderTicket(), OrderOpenPrice(), 
                        Bid - dStopLoss, OrderTakeProfit(), 0, Aqua);

                    break;
                }
            }
            
            if(OrderType() == OP_SELL)
            {
                if(OrderStopLoss() > Ask + dTrailingStop)
                {
                    OrderModify(OrderTicket(), OrderOpenPrice(), 
                        Ask + dStopLoss, OrderTakeProfit(), 0, OrangeRed);

                    break;
                }
            }
        }
    }
}

// ------

bool IsBarEnd()
{
    bool bIsBarEnd = false;
    if(nBars != Bars)
    {
        if(IsTesting() || (!IsTesting() && CurTime() > 
            Time[0] + nMagic * nDelaySeconds))
        {
            bIsBarEnd = true;
            nBars = Bars;
        }
    }
    
    return(bIsBarEnd);
}

// ------

void CheckTradeSemaphore()
{
    if(!IsTesting())
    {
        while(!IsStopped())
        {
            GlobalVariableSetOnCondition(strTradeSemaphore, nMagic, 0.0);

            if(GlobalVariableGet(strTradeSemaphore) == nMagic)
                break;

        
            Sleep(1000);
        }
    
        RefreshRates();
    }
}

// ------

void CloseBuy(string strExpertName)
{
    int nTicket = OrderTicket();
    SaveComment("\r\n\tAttempting to close long position, 
        ticket: " + nTicket + "\r\n");
    
    for(int nTry = 0; nTry < 10; nTry++)
    {
        SaveComment(Day() + "." + Month() + "." + Year() + " " + 
            Hour() + ":" + Minute() + ":" + Seconds());
        int nResult = OrderClose(OrderTicket(), OrderLots(), Bid, nSlip, Aqua);
        
        Sleep(10000);
        if(nResult == -1)
        {
            int nError = GetLastError();
            Alert(strExpertName + ", error: " + nError);
            SaveComment(strExpertName + ", error: " + nError);
        }
        
        bool bClosed = true;
        for(int nOrderNo = OrdersTotal() - 1; nOrderNo >= 0; nOrderNo--)
        {
            OrderSelect(nOrderNo, SELECT_BY_POS, MODE_TRADES);
            if(OrderTicket() == nTicket)
            {
                bClosed = false;
                break;
            }
        }
        
        if(bClosed == true)
        {
            SaveComment("\r\n\tNo more orders with this ticket No");
            break;
        }
        else
        {
            SaveComment("\r\n\tOrder with this ticket still present, trying again");
            RefreshRates();
        }
    }
}

// ------

void CloseSell(string strExpertName)
{
    int nTicket = OrderTicket();
    SaveComment("\r\n\tAttempting to close short position, ticket: "
         + nTicket + "\r\n");
    
    for(int nTry = 0; nTry < 10; nTry++)
    {
        SaveComment(Day() + "." + Month() + "." + Year() + 
            " " + Hour() + ":" + Minute() + ":" + Seconds());
        int nResult = OrderClose(OrderTicket(), OrderLots(), Ask, 
            nSlip, OrangeRed);
        
        Sleep(10000);
        if(nResult == -1)
        {
            int nError = GetLastError();
            Alert(strExpertName + ", error: " + nError);
            SaveComment(strExpertName + ", error: " + nError);
        }
        
        bool bClosed = true;
        for(int nOrderNo = OrdersTotal() - 1; nOrderNo >= 0; nOrderNo--)
        {
            OrderSelect(nOrderNo, SELECT_BY_POS, MODE_TRADES);
            if(OrderTicket() == nTicket)
            {
                bClosed = false;
                break;
            }
        }
        
        if(bClosed == true)
        {
            SaveComment("\r\n\tNo more orders with this ticket No");
            break;
        }
        else
        {
            SaveComment("\r\n\tOrder with this ticket still present, trying again");
            RefreshRates();
        }
    }
}

// ------

void SaveComment(string strComment)
{
    if(!IsTesting())
    {
        int hFile = FileOpen("__test_" + strExpert + "_" + Symbol() + ".txt", 
            FILE_BIN | FILE_READ | FILE_WRITE, '\t');    
    
        FileSeek(hFile, 0, SEEK_END);
        FileWriteString(hFile, strComment, StringLen(strComment));
        // FileFlush(hFile);
    
        FileClose(hFile);
    }
}

// ------

int DeletePending()
{
    int nResult = -1;
    
    for(int nTry = 0; nTry < 10; nTry++)
    {
        SaveComment("\r\n" + Day() + "." + Month() + "." + Year() + 
            " " + Hour() + ":" + Minute() + ":" + Seconds());
        SaveComment(" Trying to delete pending " + OrderTicket() + 
            ", attempt " + nTry + "\r\n");
        
        nResult = OrderDelete(OrderTicket());
        
        Sleep(10000);
        if(nResult != -1)
        {
            SaveComment(" successfull\r\n");
            break;
        }
        else
            SaveComment(" failed, error " + GetLastError() + "\r\n");
    }    
    
    if(nResult == -1)
    {
        int nError = GetLastError();
        Alert(strExpert + " delete pending, error: " + nError + "\r\n");
        SaveComment(strExpert + " delete pending, error: " + nError + "\r\n");
    }
    
    return(nResult);
}


Expert: _forex_nn.mql

extern double dBuyLevel;
extern double dSellLevel;
extern double dStopLoss;

// ------

double dTrailingStop;
double dTakeProfit = 0;

bool bUseMm = false;

string strExpert = "forex_nn";

// ------

#include "mylib.mq4"

// ------

int init ()
{
    nBars = Bars;

    // ------

    if(Symbol() == "EURUSD" && Period() == 60)
    {
        if(!IsTesting())                    
        {                                        
            dBuyLevel = 0.16;        
            dSellLevel = 0.9;
                                    
            dStopLoss = 2000 * Point;        
            
            // In the som_06.tsc, dStopIncrease == 0.6 
            dTrailingStop = dStopLoss / 0.6;
        }
        else
        {
            dStopLoss = dStopLoss * Point;
            dTrailingStop = dStopLoss / 0.6;
        }
        
        nMagic = 20;
    }

    return(0);
}
// ------
int deinit()
{
    return(0);
}

// ------

int start()
{
    if(Bars < 200)
        return(0);
    
//    Report(strExpert, nMagic, bReportDone);

    // ------

    if(!IsBarEnd())
        return(0);

    // ------
    
    double dNoc = iCustom(NULL, 0, "_Forex_Nn_Ind", 1, 1);
    double dNocPrev = iCustom(NULL, 0, "_Forex_Nn_Ind", 1, 2);
    
//Comment(Day(), "-", Month(), "-", Year(), " ", Hour(), ":", 
//    Minute(), " - Close: ", Close, ", NocPrev: ", dNocPrev, 
//    ", Noc: ", dNoc);    
        
    if(bUseMm == true)
    {
        dProfit = 0;
        
        for(int nCnt = 0; nCnt < HistoryTotal(); nCnt++)
        {
            OrderSelect(nCnt, SELECT_BY_POS, MODE_HISTORY);
            if(OrderMagicNumber() == nMagic && OrderType() <= OP_SELL)
            {
                dProfit += OrderProfit();
            }
        }   
     }
     
    for(nCnt = OrdersTotal() - 1; nCnt >= 0; nCnt--)
    {
        OrderSelect(nCnt, SELECT_BY_POS, MODE_TRADES);
        if(OrderMagicNumber() == nMagic)
        {
            if(OrderType() == OP_BUY)
            {         
                if(dNocPrev >= dSellLevel && dNoc <= dSellLevel)
                {
//Comment(Day(), "-", Month(), "-", Year(), " ", Hour(), ":", Minute(), 
//    " - Close: ", Close, ", CloseBuy: ", dNocPrev, " >= ", dSellLevel, 
//    " >= ", dNoc);                
                    CloseBuy(strExpert);
                    break;
                }
            }
            else if(OrderType() == OP_SELL)
            {
                   if(dNocPrev <= dBuyLevel && dNoc >= dBuyLevel)
                {
//Comment(Day(), "-", Month(), "-", Year(), " ", Hour(), ":", Minute(), 
//    " - Close: ", Close, ", CloseSell");                    
                    CloseSell(strExpert);
                    break;
                }
            }
        }
    }

     int nNumOfOpenedOrders = 0;
    for(nCnt = OrdersTotal() - 1; nCnt >= 0; nCnt--)
    {
        OrderSelect(nCnt, SELECT_BY_POS, MODE_TRADES);
        if(OrderMagicNumber() == nMagic)
            nNumOfOpenedOrders++;
    }

    if(nNumOfOpenedOrders == 0)
    {
        if(dNocPrev <= dBuyLevel && dNoc >= dBuyLevel) 
        {
//Comment(Day(), "-", Month(), "-", Year(), " ", Hour(), ":", Minute(), 
//    " - Close: ", Close, ", Buy: ", dNocPrev, " <= ", dBuyLevel, 
//    " <= ", dNoc);
            Buy(strExpert);
        }
        else if(dNocPrev >= dSellLevel && dNoc <= dSellLevel) 
        {
//Comment(Day(), "-", Month(), "-", Year(), " ", Hour(), ":", Minute(), " - Sell");
            Sell(strExpert);
        }
    }
        
    // ------

    ModifyOrders();
    
    // ------
    
    return(0);
}

// ------



Indicator: _forex_nn_ind.mq4

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1 Red
#property indicator_color2 Yellow
#property indicator_minimum 0
#property indicator_maximum 1

// indicator parameters
// To do: read from file

int nNocInterval = 12;
double dNocRange = 0.004;
int nNocMa = 3;
int nOutLag = 2;

int nLayers = 3;
int arrNeurons[3] = { 17, 3, 1 };
int nNumOfLags = 17;
int arrLags[17] = 
    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };

double arrWeights_0[306];
double arrWeights_1[54];
double arrWeights_2[4];


double arrOutput_0[17];
double arrOutput_1[3];
double dNnOutput;

double arrPattern[18];

int nRemoveFirst = 200;
double dE = 2.7182818;

// indicator buffers
double arrNocBuffer[];
double arrNnBuffer[];

int nExtCountedBars = 0;

////////////////////////
int init()
{
    // Layer: 0
    arrWeights_0[0] = 877.529097;
    arrWeights_0[1] = -979.236938;
    arrWeights_0[2] = 397.873154;
    ...
    arrWeights_0[303] = -66.428302;
    arrWeights_0[304] = 35.207516;
    arrWeights_0[305] = 339.042214;

    // Layer: 1
    arrWeights_1[0] = 6.018999;
    arrWeights_1[1] = 5.440863;
    arrWeights_1[2] = 8.927424;
    ...
    arrWeights_1[51] = -31.510057;
    arrWeights_1[52] = 7.666437;
    arrWeights_1[53] = 58.141611;

    // Layer: 2
    arrWeights_2[0] = 5.346615;
    arrWeights_2[1] = 5.275201;
    arrWeights_2[2] = 6.543355;
    arrWeights_2[3] = 8.811740;

    // drawing settings
    SetIndexStyle(0, DRAW_LINE);
    SetIndexShift(0, 0);
    SetIndexEmptyValue(0, 0.0);
        
    SetIndexStyle(1, DRAW_LINE);
    SetIndexShift(1, 0);
    SetIndexEmptyValue(1, 0.0);

    IndicatorDigits(4);
        
    string strIndicatorShortName = "forex_nn";
    IndicatorShortName(strIndicatorShortName);

    // indicator buffers mapping
    SetIndexBuffer(0, arrNocBuffer);
    SetIndexBuffer(1, arrNnBuffer);    

    return(0);
}
////////////////////
int start()
{
    nExtCountedBars = IndicatorCounted();
    if(nExtCountedBars < 0) 
        return(-1);

    // last counted bar will be recounted
    if(nExtCountedBars > 0) 
        nExtCountedBars--;
        
    Noc();        
    ApplyNn();  

    return(0);
}
///////////////////
// For the selected period:
// CLV = ((Close - Low) - (High - Close)) / (High - Low) 
void Noc()
{
    int nPos = Bars - nExtCountedBars;

    while(nPos > 0)
    {
        if(nPos > Bars - nRemoveFirst)
        {
            arrNocBuffer[nPos] = 0.5;
            nPos--;
            continue;
        }

        double dClose = Close[nPos];
        double dLow = Low[ArrayMinimum(Low, nNocInterval, nPos)]; 
        double dHigh = High[ArrayMaximum(High, nNocInterval, nPos)]; 

        arrNocBuffer[nPos] = 0.1;

        if(dHigh - dLow > dNocRange)
            arrNocBuffer[nPos] = (((dClose - dLow) - (dHigh - dClose)) 
                / (dHigh - dLow)) / 2 + 0.5; 
        else
            arrNocBuffer[nPos] = (((dClose - dLow) - (dHigh - dClose)) 
                / dNocRange) / 2 + 0.5; 

        nPos--;
    }
    
    if(nNocMa > 1)
        Ema(arrNocBuffer);
}
///////////////////
void Ema(double& arr[])
{
    double dPr = 2.0 / (nNocMa + 1);

    int nPos = Bars - nExtCountedBars;
    
    while(nPos > 0)
    {
        arrNocBuffer[nPos] = 
            arr[nPos] * dPr + arrNocBuffer[nPos + 1] * (1 - dPr);
        nPos--;
    }
}
///////////////////
void ApplyNn()
{
//int hFile = FileOpen("test.txt", FILE_CSV|FILE_WRITE, ";");

    int nPos = Bars - nExtCountedBars;// - 2;

    while(nPos > 0)
    {
        if(nPos > Bars - nRemoveFirst)
        {
            arrNnBuffer[nPos] = 0.5;
            nPos--;
            continue;
        }

        arrNnBuffer[nPos] = 0.5;
        
        for(int nLagNo = 0; nLagNo < nNumOfLags; nLagNo++)
            arrPattern[nLagNo] = arrNocBuffer[nPos + arrLags[nLagNo] + nOutLag];
            
        for(int nLayer = 0; nLayer < nLayers; nLayer++)
        {
            // Absolute index of the weight in an array
            int nWeightIdx = 0;

            for(int nNeuron = 0; nNeuron < arrNeurons[nLayer]; nNeuron++)
            {
                double dLinearCombiner = 0;
    
                int nInputs = arrNeurons[nLayer];
                if(nLayer != 0)
                    nInputs = arrNeurons[nLayer - 1];
                
                for(int nInput = 0; nInput < nInputs; nInput++)
                {
                    double dInput;
                    double dWeight;
                    switch(nLayer)
                    { 
                        case 0:
                        {
                            dInput = arrPattern[nInput];
                            dWeight = arrWeights_0[nWeightIdx];
                        }
                        break;
                        
                        case 1:
                        {
                            dInput = arrOutput_0[nInput];
                            dWeight = arrWeights_1[nWeightIdx];
                        }
                        break;
                        
                        default:
                        {
                            dInput = arrOutput_1[nInput];
                            dWeight = arrWeights_2[nWeightIdx];
                        }
                        break;
                    }

                    dLinearCombiner += dWeight * dInput;
                    nWeightIdx++;
                }        

                switch(nLayer)
                {
                    case 0:
                        dWeight = arrWeights_0[nWeightIdx]; break;
                    case 1:
                        dWeight = arrWeights_1[nWeightIdx]; break;
                    default: 
                        dWeight = arrWeights_2[nWeightIdx]; break;
                }

                dLinearCombiner += (-1) * dWeight;
                nWeightIdx++;

                double dActivation = Activation(dLinearCombiner / (nInputs + 1), 0);

                switch(nLayer)
                {
                    case 0:
                        arrOutput_0[nNeuron] = dActivation; break;
                    case 1:
                        arrOutput_1[nNeuron] = dActivation; break;
                    default: 
                        dNnOutput = dActivation; break;    // 1 neuron in the last layer
                }

            }    // for all neurons

        }    // for all layers

        arrNnBuffer[nPos] = dNnOutput;
//FileWrite(hFile, nPos, TimeDay(Time[nPos]), TimeMonth(Time[nPos]), 
//TimeYear(Time[nPos]), TimeHour(Time[nPos]), ">>>>>", 
//   Close[nPos], arrNnBuffer[nPos], nInputs, dLinearCombiner, dActivation); 

        nPos--;
            
    }    // for all patterns
    
//FileClose(hFile);        
}
///////////////////

double Activation(double u, int nType)
{
    double dPow = MathPow(dE, 2 * u / 2);
    double dEx = (dPow - 1) / (dPow + 1);
    if(dEx == 1 || dEx == -1)
        dEx = dEx * 0.999999999;
    
    // 0 - sigmoid, 1 - tangent
    if(nType == 0)
        return((dEx + 1) / 2);

    return(dEx);
}

Kohonen Neural Network + FFBP Neural Network, no cycles

We are done with a forex trading system that uses FFBP networks only. Now we are going to do the same work with a forex trading strategy, that uses FFBP and Kohonen SOM together.

First of all, let's take som_07.tsc again, and run it, in order to pick the best FFBP network. Note, that the Kohonen clustering map for this script was created earlier (in som_04), and stored as som_07_winer.kh, so we do not have to recreate it.

som_07.tsc

Trades: 52(Buy: 52, Sell :0)
NocInterval: 12, Range: 0.004, Ma: 3 Lag: 2,
Neurons: 3
Stop: 0.0200, Tp: 0.0000, Stop increase: 0.600000,
Buy: 0.170, Sell: 0.900
Drawdown: 0.981 Profit: 6820.000000 (long: 6820.000000, short: 0.000000)


Then, let's remove cycles from som_07.tsc, to get the script we are going to port:

som_10.tsc

void main()
{
    OUT_CLEANUP();
    
    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    PRINT("%s\r\n", "Deleting image files...");

    array_s arrDirList = GET_DIR(strImagePath, 0, "*.png");
    for(double n = 0; n < ARRAY_SIZE(arrDirList); n = n + 1)
    {
        F_UNLINK(arrDirList[n]);
    }

    // ------ First, let's handle SOM
    
    double hFile;
    double i;

    // ***** Loading data
    string strDataFileName = "..\\CortexPro\\data\\samples\\forex\\eurusd_h1.txt";
    double bIsPathRelative = 1;

    array arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    PRINT("%s\r\n", "Loading data");

    TABLE_LOADER(strDataFileName, bIsPathRelative, 0, "", 0, "", 0, 
        arrDate, 1, arrTime, 2, arrOpen, 3, arrHigh, 4, arrLow, 5, arrClose);
    arrTime = arrTime / (24 * 60);
    arrDate = arrDate + arrTime;

    array_s arrSomParameters = CREATE_ARRAY_S(0);
    arrSomParameters[0] = "128,0,2,2";    

    array arrSomLags = CREATE_ARRAY(0);
    array_s arrSomStrLags = CREATE_ARRAY_S(0);

    arrSomStrLags[0] = "17,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16";

    array arrColumnNumbers = CREATE_ARRAY(0);
    
    double nRemoveFirst = 200;
    
    string strSomLagFileName = "..\\CortexPro\\data\\som\\tmp\\som_10.lgg";
    string strSomFileName = "..\\CortexPro\\data\\som\\nn\\som_07_winner.kh";

    string strSomParam;
    string strSomToken;
    double nSomInterval;
    double nSomMa;
    array arrSomNocRaw = CREATE_ARRAY(0);
    array arrSomNoc = CREATE_ARRAY(0);

    double nSomParIdx = 0;

    strSomParam = arrSomParameters[nSomParIdx];
    strSomToken = GET_TOKEN(strSomParam, ",");
    nSomInterval = STR2NUM(strSomToken);

    strSomToken = GET_TOKEN(strSomParam, ",");
    double dSomRange = STR2NUM(strSomToken);

    ARRAY_REMOVE(arrSomNoc, -1);
    arrSomNocRaw = CreateNoc(nSomInterval, dSomRange);
        
    strSomToken = GET_TOKEN(strSomParam, ",");
    nSomMa = STR2NUM(strSomToken);
    arrSomNoc = EXP_MVAVG(arrSomNocRaw, nSomMa);
        
    strSomToken = GET_TOKEN(strSomParam, ",");
    double nSomOutLag = STR2NUM(strSomToken);
            
    double nSomLagIdx = 0;
    double nSomNumOfLags;
    string strSomLagBuf = arrSomStrLags[nSomLagIdx];
        
    CreateSomLagFile(strSomLagBuf, nRemoveFirst);
                                        
    double hSom = OPEN_SOM(strSomFileName , bIsPathRelative);
                
    GET_SOM_WINNERS(hSom, strSomLagFileName, bIsPathRelative, "", 1, "", arrColumnNumbers, 1);

    double bMode = 1;
    array arrColors = APPLY_SOM(hSom, strSomLagFileName, bIsPathRelative, bMode, "", 1, "", arrColumnNumbers);
                
    CLOSE_SOM(hSom);

    // ------ Now let's handle FFBP NN
        
    string strForexName = "EURUSD_H1";
    string strNnFileName = "..\\CortexPro\\data\\som\\nn\\som_07_winner.nn";

    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\" 
            + strForexName + ".TXT";
    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\"
            + strForexName + "_nn_10.lgg";
    
    double nArraySize = ARRAY_SIZE(arrClose);

    // ---

    array_s arrParameters = CREATE_ARRAY_S(0);
    array arrNeurons = CREATE_ARRAY(0);
    array arrLags = CREATE_ARRAY(0);
    array_s arrStrLags = CREATE_ARRAY_S(0);
    array arrStopLoss = CREATE_ARRAY(0);
    
    arrParameters[0] = "12,0,3,2";
    arrNeurons[0] = 3;
    double dStopIncrease = 0.3;
    double dBuyLevel = 0.150;
    arrStrLags[0] = "17,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16";
    arrStopLoss[0] = 0.02;

    array arrNoc;
    array arrNocSmooth;

    double dStopError = 0;
    double nStopEpoch = 5000; 

    array arrNn = CREATE_ARRAY(0);

    string strParam;
    string strToken;
    double nMa;
    double hNn;
    double nExtractRecords;

    // ------

    double nNetNum = 0;
    double nCounter = 0;
    double dExpectedCycles = ARRAY_SIZE(arrParameters)
        * ARRAY_SIZE(arrStrLags) * ARRAY_SIZE(arrNeurons);

    string strResult = "";
    
    double dErrorMin = -1;

    double bSuccess;
    double nWinners = 0;
    double nTradeNumber;

    array arrBalance = CREATE_ARRAY(0);
    array arrBalanceBuy = CREATE_ARRAY(0);
    array arrBalanceSell = CREATE_ARRAY(0);

    string strXML = "<forex>\r\n";
    
    double nImageNum = 0;

    for(double nParIdx = 0; nParIdx < ARRAY_SIZE(arrParameters); 
        nParIdx = nParIdx + 1)
    {
        strParam = arrParameters[nParIdx];
        strToken = GET_TOKEN(strParam, ",");
        double nInterval = STR2NUM(strToken);

        strToken = GET_TOKEN(strParam, ",");

        double dRange = STR2NUM(strToken);

        arrNoc = CreateNoc(nInterval, dRange);

        strToken = GET_TOKEN(strParam, ",");
        nMa = STR2NUM(strToken);

        arrNocSmooth = EXP_MVAVG(arrNoc, nMa);

        strToken = GET_TOKEN(strParam, ",");
        double nOutLag = STR2NUM(strToken);
        
        for(double nLagIdx = 0; nLagIdx < ARRAY_SIZE(arrStrLags); 
            nLagIdx = nLagIdx + 1)
        {
            double nNumOfLags;
            string strLagBuf = arrStrLags[nLagIdx];
            CreateLagFile(strLagBuf, nRemoveFirst);

            for(double nNeuronsIdx = 0; nNeuronsIdx < ARRAY_SIZE(arrNeurons); 
                nNeuronsIdx = nNeuronsIdx + 1)
            {
                nCounter = nCounter + 1;

                double nNeurons = arrNeurons[nNeuronsIdx];
                        
                PRINT("%.0f", nCounter, " of %.0f\r\n", dExpectedCycles);

//                NewNn(arrLags, dStopError, nStopEpoch, nNeurons);
//                TeachNn();

                hNn = OPEN_NN(strNnFileName, bIsPathRelative);

                APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrNocSmooth, arrLags, 1, arrNn);

                double nStopIdx = 0;
//                for(double nStopIdx = 0; nStopIdx < ARRAY_SIZE(arrStopLoss); nStopIdx = nStopIdx + 1)
//                {
                    double dStopLoss = arrStopLoss[nStopIdx];
                    double dTakeProfit = 0;
                                
//                    for(double dStopIncrease = 0.1; dStopIncrease < 0.7; dStopIncrease = dStopIncrease + 0.1)
//                    {
                        OUT_CLEANUP();

//                        for(double nBuyIdx = 0; nBuyIdx < 30; nBuyIdx = nBuyIdx + 1)
//                        {
                            nImageNum = nImageNum + 1;
                            
//                            double dBuyLevel = 0.1 + 0.01 * nBuyIdx;

//                            for(double nSellIdx = 0; nSellIdx < 30; nSellIdx = nSellIdx + 1)
//                            {
//                                double dSellLevel = 0.9 - 0.01 * nSellIdx;
                                double dSellLevel = 0.9;

                                PRINT("%.0f", nCounter, " of %.0f", dExpectedCycles);

                                Test();
                                                                        
                                if(bSuccess == 0)
                                {
                                    PRINT(" (%.0f)\r\n", nWinners);
//                                    continue nSellIdx;
//                                    continue nNeuronsIdx;
                                }
    
                                nWinners = nWinners + 1;
                                PRINT(" (%.0f)\r\n", nWinners);    
                                                
                                Chart(strForexName);
//                            }
//                        }
//                    }
//                }
            }
        }
    }

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", "chart_forex_som", "root", strXML);
//    SHOW_XML(strImagePath + "chart_forex_nn.xml");

    PRINT("%s\r\nDone\r\n", strResult);
}

// ---------------

void Test()
{
    bSuccess = 1;
    
    ARRAY_REMOVE(arrBalance, -1);
    arrBalance[0] = 1000;

    ARRAY_REMOVE(arrBalanceBuy, -1);
    arrBalanceBuy[0] = 0;

    ARRAY_REMOVE(arrBalanceSell, -1);
    arrBalanceSell[0] = 0;

    array arrBars = CREATE_ARRAY(0);
    arrBars[0] = 0;

    double dLotSize = 100;        // 0.1 lot
    double nType = 0;            // 1 buy, -1 sell, 0 - none

    double dSpread = 0.0005;
    double bStop;

    double dMaxDrawDown = 0;            // Max. Drawdown
    double dCurrentMax = 1000;        // Used to calculate drawdown

    nTradeNumber = 0;
    double nTradeNumberBuy = 0;
    double nTradeNumberSell = 0;
    double dOpenPrice;
    double dStop;
    double dTp = 0;

    for(double nBar = nRemoveFirst + 1; 
        nBar < ARRAY_SIZE(arrClose) - 1; nBar = nBar + 1)
    {
        if(nType != 0)
        {
            bStop = 0;
            double dClosedAt;

            // If BUY and stop loss or take profit reached
            if(nType == -1 && (arrLow[nBar] <= dStop || (dTakeProfit > 0 
                && arrHigh[nBar] >= dTp - dSpread)))
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1]; 

                bStop = 1;
                dClosedAt = arrLow[nBar + 1];
            }
            else
            {
                if(nType == 1 && (arrHigh[nBar] >= dStop - dSpread ||
                    (dTakeProfit > 0 && arrLow[nBar] <= dTp)))
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] = 
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    bStop = 1;
                    dClosedAt = arrHigh[nBar + 1];
                }
            }

            if(bStop == 1)
            {
                nType = 0;
                arrBars[ARRAY_SIZE(arrBars)] = nBar;
            }
        }

        double dDrawDown = (dCurrentMax - 
            arrBalance[ARRAY_SIZE(arrBalance) - 1]) / 1000; //dCurrentMax; 
        dMaxDrawDown = MAX(dMaxDrawDown, dDrawDown);
        dCurrentMax = MAX(dCurrentMax, arrBalance[ARRAY_SIZE(arrBalance) - 1]);

        if(dMaxDrawDown > 1)
        {
            bSuccess = 0;
            break nBar;
        }

        if(nType != -1 && arrNn[nBar - 1] <= dBuyLevel && arrNn[nBar] >= dBuyLevel)
        {
            if(nType == 1)
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                arrBars[ARRAY_SIZE(arrBars)] = nBar;

                nType = 0;
            }

            dOpenPrice = arrHigh[nBar + 1];
            dStop = dOpenPrice - dStopLoss;
            dTp = dOpenPrice + dTakeProfit;
            nType = -1;
            
            nTradeNumber = nTradeNumber + 1;
            nTradeNumberBuy = nTradeNumberBuy + 1;
        }
        else
        {
            if(nType != 1 && arrNn[nBar - 1] >= dSellLevel && arrNn[nBar] <= dSellLevel)
            {
                if(nType == -1)
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] =
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] =
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] =
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];

                    arrBars[ARRAY_SIZE(arrBars)] = nBar;
                
                    nType = 0;
                }
            }
        }

        if(dStopIncrease != 0)
        {
            if(nType == -1)
            {
                if(arrLow[nBar] - dStop >= dStopLoss * (1 + dStopIncrease))
                {
                    dStop = arrLow[nBar] - dStopLoss;
                }
            }
            else
            {
                if(nType == 1)
                {
                    if(dStop - arrHigh[nBar] >= dStopLoss * (1 + dStopIncrease))
                    {
                        dStop = arrHigh[nBar] + dStopLoss;
                    }
                }
            }
        }
    }

    // If at the end we have open positions, close them

    if(nType == 1)
    {
        arrBalance[ARRAY_SIZE(arrBalance)] = 
            arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

        arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;
    }
    else
    {
        if(nType == -1)
        {
            arrBalance[ARRAY_SIZE(arrBalance)] = 
                arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];
    
            arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;

        }
    }

    if(ARRAY_SIZE(arrBalance) <= 1 || 
        (arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] <= 500 
            && arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] <= 200) ||
        nTradeNumber < 20)
    {
        bSuccess = 0;
    }
}

// ---------------

array CreateNoc(double nInterval, double dMinRange)
{    
    PRINT("%s\r\n", "Creating NOC indicator");

    array arrNormOnCondition = CREATE_ARRAY(0);
    array arrPeriodLow = CREATE_ARRAY(0);
    array arrPeriodHigh = CREATE_ARRAY(0);

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MV_MIN(arrLow, nInterval);
    array arrPeriodHigh = MV_MAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrNormOnCondition[i] = 0;
    }
    
    double dClose;
    double dLow;
    double dHigh;
    for(i = nInterval; i < nArraySize; i = i + 1)
    {
        dClose = arrClose[i-1];
        dLow = arrPeriodLow[i-1];
        dHigh = arrPeriodHigh[i-1];

        if(dHigh - dLow > dMinRange)
        {
            // / 2 + 1 to confine to 0...1 instead of -1...1
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / (dHigh - dLow)) / 2 + 0.5; 
        }
        else
        {
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / dMinRange) / 2 + 0.5; 
        }
    }

    return arrNormOnCondition;
}

// ------

void CreateSomLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strSomLagFileName, "wb", bIsPathRelative);
    F_PRINT(hFile, "%s", "X,Y");

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    ARRAY_REMOVE(arrSomLags, -1);
    ARRAY_REMOVE(arrColumnNumbers, -1);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrSomLags[i] = STR2NUM(strToken);

        F_PRINT(hFile, ",Y-%.0f", arrSomLags[i]); 
        arrColumnNumbers[i] = i+1;
    }
    F_PRINT(hFile, "%s", "\r\n");

    for(double i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        F_PRINT(hFile, "%.0f", i - nRemoveFirst + 1);
        for(double j = 0; j < ARRAY_SIZE(arrSomLags); j = j + 1)
        {
            F_PRINT    (hFile, ",%f", arrSomNoc[i - arrSomLags[j]]);      
        }

        F_PRINT(hFile, "%s", "\r\n");
    }

    F_CLOSE(hFile);
}


// ------

void CreateLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strLagFileName, "wb", bIsPathRelative);

    F_PRINT(hFile, "%s", "No,Clv");

    ARRAY_REMOVE(arrLags, -1);

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrLags[i] = STR2NUM(strToken) + nOutLag;
    }

    double nFullLag;
    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        nFullLag = arrLags[i];
        F_PRINT(hFile, ",NocMa%.0f", nMa, "-%.0f", nFullLag); 
    }
    
    F_PRINT(hFile, "red,green,blue%s\r\n", "");

    double nNum;
    for(i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        nNum = i - nRemoveFirst + 1;
        F_PRINT(hFile, "%.0f", nNum, ",%f", arrNocSmooth[i]);     

        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrNocSmooth[i - arrLags[j]]);     
        }

        for(double k = 0; k < 3; k = k + 1)
        {
            F_PRINT(hFile, ",%f", arrColors[3 * i + k]);
        }

        F_PRINT(hFile, "%s\r\n", "");
    }

    F_CLOSE(hFile);
}

// --------------

void NewNn(array arrLags, double dStopError, double nStopEpoch, double nNeuronsLayer2)
{
    double nSkipBefore = 0;
    double nSkipAfter = 0;
    string strStartLine = "";
    string strEndLine = "";
    double bReverseArrays = 0;

    // Inputs
    array arrInputColumns = CREATE_ARRAY(0);
    array_s arrInputColumnNames = CREATE_ARRAY_S(0);
    
    // 0  - Number, 1 - Clv, our input begins at column No 2
    for(double nInputCol = 0; nInputCol < ARRAY_SIZE(arrLags); nInputCol = nInputCol + 1) 
    {
        arrInputColumns[nInputCol] = nInputCol + 2;
        arrInputColumnNames[nInputCol] = 
            "Clv-" + NUM2STR(arrLags[nInputCol], "%.0f"); 
    }

    array arrOutputColumns = CREATE_ARRAY(0);
    arrOutputColumns[0] = 1;    // Noc

    array_s arrOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutputColumnNames[0] = "Noc";

    nExtractRecords = 0.7 * ARRAY_SIZE(arrClose);

    double nNeuronsLayer1 = ARRAY_SIZE(arrLags);
//    double nNeuronsLayer2 = 7;
    double nNeuronsLayer3 = 1;
    double nNeuronsLayer4 = 0;
    double nLayers = 3; 
    double nActivation = 0;
    double nAdjustRange = 1.0;

    array arrOutTabInputColumns = CREATE_ARRAY(0);
    arrOutTabInputColumns[0] = 0;    // Number

    array_s arrOutTabInputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabInputColumnNames[0] = "No";

    array arrOutTabOutputColumns = CREATE_ARRAY(0);
    // Desired output and NN output will be added to the 
    // same list, right after inputs
    arrOutTabOutputColumns[0] = ARRAY_SIZE(arrLags) + 2;    
    arrOutTabOutputColumns[1] = ARRAY_SIZE(arrLags) + 3;

    array_s arrOutTabOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabOutputColumnNames[0] = "Noc"; 
    arrOutTabOutputColumnNames[1] = "NN: Noc" ;

    CREATE_NN(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), bIsPathRelative, strLagFileName, 
        bIsPathRelative, nSkipBefore, nSkipAfter, strStartLine, strEndLine,
        bReverseArrays, arrInputColumns, arrInputColumnNames, 
        arrOutputColumns, arrOutputColumnNames, nExtractRecords, dStopError, nStopEpoch, 
        nNeuronsLayer1, nNeuronsLayer2, nNeuronsLayer3, nNeuronsLayer4, nLayers, 
        nActivation, nAdjustRange, arrOutTabInputColumns, arrOutTabInputColumnNames,
        arrOutTabOutputColumns, arrOutTabOutputColumnNames);
}

// ----------------

void TeachNn()
{
    PRINT("%s\r\n", "Opening NN dialog, teaching the NN");

    double bStartLearning = 1; 
    double bResumeScript = 1;
    double bReset = 1;

    OPEN_NN_FILE(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), 
        bIsPathRelative, bStartLearning, bResumeScript, bReset);
}


// -------------

void Chart(string strForexName)
{
    string strWinNnFileName;
        
    string strImageFileName = strImagePath + strForexName + 
        NUM2STR(nImageNum, "_%.0f") + "_nn_10.png";    
            
    strXML = strXML + "\t<symbol>\r\n\t\t<symbol>\r\n";

    strXML = strXML + "NN: " + NUM2STR(nCounter, "%.0f, ")
        + "Trades: " + NUM2STR(nTradeNumber, "%.0f")
        + "(Buy: " + NUM2STR(nTradeNumberBuy, "%.0f")
        + ", Sell :" + NUM2STR(nTradeNumberSell, "%.0f)\r\n")
        + "NocInterval: " + NUM2STR(nInterval, "%.0f")
        + ", Range: " + NUM2STR(dRange, "%.3f")
        + ", Ma: " + NUM2STR(nMa, "%.0f\r\n")

        + "Lag: " + NUM2STR(nOutLag, "%.0f")
        + ", Neurons: " + NUM2STR(nNeurons, "%.0f\r\n")
        + "Stop: " + NUM2STR(dStopLoss, "%.4f")
        + ", Tp: " + NUM2STR(dTakeProfit, "%.4f")
        + NUM2STR(dStopIncrease, ", Stop increase: %f")
        + ", Buy: " + NUM2STR(dBuyLevel, "%.3f")
        + ", Sell: " + NUM2STR(dSellLevel, "%.3f\r\n")

        + "Drawdown: " + NUM2STR(dMaxDrawDown, "%.3f\r\n")

        + "Profit: " + NUM2STR(arrBalance[ARRAY_SIZE(arrBalance) - 1] - 1000, "%f")
        + " (long: " + NUM2STR(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1], "%f")
        + ", short: " + NUM2STR(arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1], "%f)\r\n");

    strXML = strXML + "\t\t</symbol>\r\n";

    strXML = strXML + "\t\t" + SAVE_CHART(400, 300, 0, strImageFileName, 
        arrBars, arrBalance, arrBalanceBuy, arrBalanceSell);
    strXML = strXML + "\t</symbol>\r\n";
}

Exporting weights of the Self-Organizing Map

Let's add the code to print Neural Network parameters, both for FFBP Neural Network and for self-organizing map. We do it by adding the following to som_10.tsc:

som_11.tsc, fragment

...
    GET_SOM_WINNERS(hSom, strSomLagFileName, bIsPathRelative, "", 
        1, "", arrColumnNumbers, 1);
    
    // ------ Getting SOM info
    
    array arrSomInfo = GET_SOM_INFO(hSom);
    
    double nNumOfSomNeurons = arrSomInfo[0];
    PRINT("\tdouble nSomNeurons = %.0f;\r\n", arrSomInfo[0]);
    PRINT("\tdouble nSomMatrixWidth = %.0f;\r\n", arrSomInfo[1]);
    PRINT("\tdouble nSomMatrixHeight = %.0f;\r\n", arrSomInfo[2]);
    
    array arrSomWeights = GET_SOM_WEIGHTS(hSom);
    double nNumOfSomWeights = ARRAY_SIZE(arrSomWeights) / nNumOfSomNeurons; 
    
    PRINT("\tdouble nSomWeights = %.0f;\r\n", nNumOfSomWeights);
    PRINT("\tdouble nSomTotalWeights = %.0f;\r\n", ARRAY_SIZE(arrSomWeights));
    double nIdx = 0;
    for(double i = 0; i < nNumOfSomNeurons; i = i + 1)
    {
        for(double j = 0; j < nNumOfSomWeights; j = j + 1)
        {
            PRINT("\tarrSomWeights[%.0f] = ", nIdx, "%f;\r\n", arrSomWeights[nIdx]);
            nIdx = nIdx + 1;
        }
    }

    array arrSomColors = GET_SOM_COLORS(hSom);
    for(double i = 0; i < nNumOfSomNeurons; i = i + 1)
    {
        PRINT("\tarrSomColors[%.0f] = ", i, "%.0f;\r\n", arrSomColors[i]);
    }
...

Emulating APPLY_SOM function

The next step is to add the code, that emulates "apply" function, using generic scripting language instead of Cortex-specific calls. As we already have this code for FFBP NN, let's focus on SOM only.

som_12.tsc, fragment

void main()
{
    OUT_CLEANUP();
    
    string strImagePath = 
        "h:\\S_Projects\\CortexPro\\data\\som\\images\\";

    PRINT("%s\r\n", "Deleting image files...");

    array_s arrDirList = GET_DIR(strImagePath, 0, "*.png");
    for(double n = 0; n < ARRAY_SIZE(arrDirList); n = n + 1)
    {
        F_UNLINK(arrDirList[n]);
    }

    // ------ First, let's handle SOM
    
    double hFile;
    double i;

    // ***** Loading data
    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\eurusd_h1.txt";
    double bIsPathRelative = 1;

    array arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    PRINT("%s\r\n", "Loading data");

    TABLE_LOADER(strDataFileName, bIsPathRelative, 0, "", 0, "", 0, 
        arrDate, 1, arrTime, 2, arrOpen, 3, arrHigh, 
        4, arrLow, 5, arrClose);
    arrTime = arrTime / (24 * 60);
    arrDate = arrDate + arrTime;

    array_s arrSomParameters = CREATE_ARRAY_S(0);
    arrSomParameters[0] = "128,0,2,2";    

    array arrSomLags = CREATE_ARRAY(0);
    array_s arrSomStrLags = CREATE_ARRAY_S(0);

    arrSomStrLags[0] = "17,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16";

    array arrColumnNumbers = CREATE_ARRAY(0);
    
    double nRemoveFirst = 200;
    
    string strSomLagFileName = "..\\CortexPro\\data\\som\\tmp\\som_10.lgg";
    string strSomFileName = "..\\CortexPro\\data\\som\\nn\\som_07_winner.kh";

    string strSomParam;
    string strSomToken;
    double nSomInterval;
    double nSomMa;
    array arrSomNocRaw = CREATE_ARRAY(0);
    array arrSomNoc = CREATE_ARRAY(0);

    double nSomParIdx = 0;

    strSomParam = arrSomParameters[nSomParIdx];
    strSomToken = GET_TOKEN(strSomParam, ",");
    nSomInterval = STR2NUM(strSomToken);

    strSomToken = GET_TOKEN(strSomParam, ",");
    double dSomRange = STR2NUM(strSomToken);

    ARRAY_REMOVE(arrSomNoc, -1);
    arrSomNocRaw = CreateNoc(nSomInterval, dSomRange);
        
    strSomToken = GET_TOKEN(strSomParam, ",");
    nSomMa = STR2NUM(strSomToken);
    arrSomNoc = EXP_MVAVG(arrSomNocRaw, nSomMa);
        
    strSomToken = GET_TOKEN(strSomParam, ",");
    double nSomOutLag = STR2NUM(strSomToken);
            
    double nSomLagIdx = 0;
    double nSomNumOfLags;
    string strSomLagBuf = arrSomStrLags[nSomLagIdx];
        
    CreateSomLagFile(strSomLagBuf, nRemoveFirst);
                                        
//    double hSom = OPEN_SOM(strSomFileName , bIsPathRelative);
                
//    GET_SOM_WINNERS(hSom, strSomLagFileName, bIsPathRelative, 
//        "", 1, "", arrColumnNumbers, 1);
    
    
//    double bMode = 1;
//    array arrColors = APPLY_SOM(hSom, strSomLagFileName, bIsPathRelative, 
//        bMode, "", 1, "", arrColumnNumbers);
    array arrColors = CREATE_ARRAY(0);
    ApplySom();
                
//    CLOSE_SOM(hSom);

    // ------ Now let's handle FFBP NN
        
    string strForexName = "EURUSD_H1";
    string strNnFileName = "..\\CortexPro\\data\\som\\nn\\som_07_winner.nn";

    string strDataFileName = 
        "..\\CortexPro\\data\\samples\\forex\\" 
            + strForexName + ".TXT";
    string strLagFileName = 
        "..\\CortexPro\\data\\som\\tmp\\"
            + strForexName + "_nn_10.lgg";
    
    double nArraySize = ARRAY_SIZE(arrClose);

    // ---

    array_s arrParameters = CREATE_ARRAY_S(0);
    array arrNeurons = CREATE_ARRAY(0);
    array arrLags = CREATE_ARRAY(0);
    array_s arrStrLags = CREATE_ARRAY_S(0);
    array arrStopLoss = CREATE_ARRAY(0);
    
    arrParameters[0] = "12,0,3,2";
    arrNeurons[0] = 3;
    double dStopIncrease = 0.3;
    double dBuyLevel = 0.150;
    arrStrLags[0] = "17,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16";
    arrStopLoss[0] = 0.02;

    array arrNoc;
    array arrNocSmooth;

    double dStopError = 0;
    double nStopEpoch = 5000; 

    array arrNn = CREATE_ARRAY(0);

    string strParam;
    string strToken;
    double nMa;
    double hNn;
    double nExtractRecords;

    // ------

    double nNetNum = 0;
    double nCounter = 0;
    double dExpectedCycles = ARRAY_SIZE(arrParameters)
        * ARRAY_SIZE(arrStrLags) * ARRAY_SIZE(arrNeurons);

    string strResult = "";
    
    double dErrorMin = -1;

    double bSuccess;
    double nWinners = 0;
    double nTradeNumber;

    array arrBalance = CREATE_ARRAY(0);
    array arrBalanceBuy = CREATE_ARRAY(0);
    array arrBalanceSell = CREATE_ARRAY(0);

    string strXML = "<forex>\r\n";
    
    double nImageNum = 0;

    for(double nParIdx = 0; nParIdx < ARRAY_SIZE(arrParameters); 
        nParIdx = nParIdx + 1)
    {
        strParam = arrParameters[nParIdx];
        strToken = GET_TOKEN(strParam, ",");
        double nInterval = STR2NUM(strToken);

        strToken = GET_TOKEN(strParam, ",");

        double dRange = STR2NUM(strToken);

        arrNoc = CreateNoc(nInterval, dRange);

        strToken = GET_TOKEN(strParam, ",");
        nMa = STR2NUM(strToken);

        arrNocSmooth = EXP_MVAVG(arrNoc, nMa);

        strToken = GET_TOKEN(strParam, ",");
        double nOutLag = STR2NUM(strToken);
        
        for(double nLagIdx = 0; nLagIdx < ARRAY_SIZE(arrStrLags); 
            nLagIdx = nLagIdx + 1)
        {
            string strLagBuf = arrStrLags[nLagIdx];
            CreateLagFile(strLagBuf, nRemoveFirst);

            for(double nNeuronsIdx = 0; nNeuronsIdx < ARRAY_SIZE(arrNeurons); 
                nNeuronsIdx = nNeuronsIdx + 1)
            {
                nCounter = nCounter + 1;

                double nNeurons = arrNeurons[nNeuronsIdx];
                        
                PRINT("%.0f", nCounter, " of %.0f\r\n", dExpectedCycles);

//                NewNn(arrLags, dStopError, nStopEpoch, nNeurons);
//                TeachNn();

                hNn = OPEN_NN(strNnFileName, bIsPathRelative);

                APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrNocSmooth, arrLags, 1, arrNn);

                double nStopIdx = 0;
//                for(double nStopIdx = 0; nStopIdx < ARRAY_SIZE(arrStopLoss); 
//                    nStopIdx = nStopIdx + 1)
//                {
                    double dStopLoss = arrStopLoss[nStopIdx];
                    double dTakeProfit = 0;
                                
//                    for(double dStopIncrease = 0.1; dStopIncrease < 0.7; 
//                        dStopIncrease = dStopIncrease + 0.1)
//                    {
                        OUT_CLEANUP();

//                        for(double nBuyIdx = 0; nBuyIdx < 30; nBuyIdx = nBuyIdx + 1)
//                        {
                            nImageNum = nImageNum + 1;
                            
//                            double dBuyLevel = 0.1 + 0.01 * nBuyIdx;

//                            for(double nSellIdx = 0; nSellIdx < 30; nSellIdx = nSellIdx + 1)
//                            {
//                                double dSellLevel = 0.9 - 0.01 * nSellIdx;
                                double dSellLevel = 0.9;

                                PRINT("%.0f", nCounter, " of %.0f", dExpectedCycles);

                                Test();
                                                                        
                                if(bSuccess == 0)
                                {
                                    PRINT(" (%.0f)\r\n", nWinners);
//                                    continue nSellIdx;
//                                    continue nNeuronsIdx;
                                }
    
                                nWinners = nWinners + 1;
                                PRINT(" (%.0f)\r\n", nWinners);    
                                                
                                Chart(strForexName);
//                            }
//                        }
//                    }
//                }
            }
        }
    }

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_som", 
        "chart_forex_som", "root", strXML);
//    SHOW_XML(strImagePath + "chart_forex_nn.xml");

    PRINT("%s\r\nDone\r\n", strResult);
}

// ---------------

void Test()
{
    bSuccess = 1;
    
    ARRAY_REMOVE(arrBalance, -1);
    arrBalance[0] = 1000;

    ARRAY_REMOVE(arrBalanceBuy, -1);
    arrBalanceBuy[0] = 0;

    ARRAY_REMOVE(arrBalanceSell, -1);
    arrBalanceSell[0] = 0;

    array arrBars = CREATE_ARRAY(0);
    arrBars[0] = 0;

    double dLotSize = 100;        // 0.1 lot
    double nType = 0;            // 1 buy, -1 sell, 0 - none

    double dSpread = 0.0005;
    double bStop;

    double dMaxDrawDown = 0;            // Max. Drawdown
    double dCurrentMax = 1000;        // Used to calculate drawdown

    nTradeNumber = 0;
    double nTradeNumberBuy = 0;
    double nTradeNumberSell = 0;
    double dOpenPrice;
    double dStop;
    double dTp = 0;

    for(double nBar = nRemoveFirst + 1; 
        nBar < ARRAY_SIZE(arrClose) - 1; nBar = nBar + 1)
    {
        if(nType != 0)
        {
            bStop = 0;
            double dClosedAt;

            // If BUY and stop loss or take profit reached
            if(nType == -1 && (arrLow[nBar] <= dStop || (dTakeProfit > 0 
                && arrHigh[nBar] >= dTp - dSpread)))
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] 
                    + 100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1]; 

                bStop = 1;
                dClosedAt = arrLow[nBar + 1];
            }
            else
            {
                if(nType == 1 && (arrHigh[nBar] >= dStop - dSpread ||
                    (dTakeProfit > 0 && arrLow[nBar] <= dTp)))
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] = 
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                    bStop = 1;
                    dClosedAt = arrHigh[nBar + 1];
                }
            }

            if(bStop == 1)
            {
                nType = 0;
                arrBars[ARRAY_SIZE(arrBars)] = nBar;
            }
        }

        double dDrawDown = (dCurrentMax - 
            arrBalance[ARRAY_SIZE(arrBalance) - 1]) / 1000; //dCurrentMax; 
        dMaxDrawDown = MAX(dMaxDrawDown, dDrawDown);
        dCurrentMax = MAX(dCurrentMax, arrBalance[ARRAY_SIZE(arrBalance) - 1]);

        if(dMaxDrawDown > 1)
        {
            bSuccess = 0;
            break nBar;
        }

        if(nType != -1 && arrNn[nBar - 1] <= dBuyLevel && arrNn[nBar] >= dBuyLevel)
        {
            if(nType == 1)
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar + 1] - dSpread) * dLotSize;

                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

                arrBars[ARRAY_SIZE(arrBars)] = nBar;

                nType = 0;
            }

            dOpenPrice = arrHigh[nBar + 1];
            dStop = dOpenPrice - dStopLoss;
            dTp = dOpenPrice + dTakeProfit;
            nType = -1;
            
            nTradeNumber = nTradeNumber + 1;
            nTradeNumberBuy = nTradeNumberBuy + 1;
        }
        else
        {
            if(nType != 1 && arrNn[nBar - 1] >= dSellLevel && arrNn[nBar] <= dSellLevel)
            {
                if(nType == -1)
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] =
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] =
                        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                        100 * (arrLow[nBar + 1] - dOpenPrice) * dLotSize;

                    arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] =
                        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];

                    arrBars[ARRAY_SIZE(arrBars)] = nBar;
                
                    nType = 0;
                }
            }
        }

        if(dStopIncrease != 0)
        {
            if(nType == -1)
            {
                if(arrLow[nBar] - dStop >= dStopLoss * (1 + dStopIncrease))
                {
                    dStop = arrLow[nBar] - dStopLoss;
                }
            }
            else
            {
                if(nType == 1)
                {
                    if(dStop - arrHigh[nBar] >= dStopLoss * (1 + dStopIncrease))
                    {
                        dStop = arrHigh[nBar] + dStopLoss;
                    }
                }
            }
        }
    }

    // If at the end we have open positions, close them

    if(nType == 1)
    {
        arrBalance[ARRAY_SIZE(arrBalance)] = 
            arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] + 
            100 * (dOpenPrice - arrHigh[nBar] - dSpread) * dLotSize;

        arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];

        arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;
    }
    else
    {
        if(nType == -1)
        {
            arrBalance[ARRAY_SIZE(arrBalance)] = 
                arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy)] = 
                arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] + 
                100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
    
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell)] = 
                arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];
    
            arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;

        }
    }

    if(ARRAY_SIZE(arrBalance) <= 1 || 
        (arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] <= 500 
            && arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] <= 200) ||
        nTradeNumber < 20)
    {
        bSuccess = 0;
    }
}

// ---------------

array CreateNoc(double nInterval, double dMinRange)
{    
    PRINT("%s\r\n", "Creating NOC indicator");

    array arrNormOnCondition = CREATE_ARRAY(0);
    array arrPeriodLow = CREATE_ARRAY(0);
    array arrPeriodHigh = CREATE_ARRAY(0);

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MV_MIN(arrLow, nInterval);
    array arrPeriodHigh = MV_MAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrNormOnCondition[i] = 0;
    }
    
    double dClose;
    double dLow;
    double dHigh;
    for(i = nInterval; i < nArraySize; i = i + 1)
    {
        dClose = arrClose[i-1];
        dLow = arrPeriodLow[i-1];
        dHigh = arrPeriodHigh[i-1];

        if(dHigh - dLow > dMinRange)
        {
            // / 2 + 1 to confine to 0...1 instead of -1...1
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / (dHigh - dLow)) / 2 + 0.5; 
        }
        else
        {
            arrNormOnCondition[i] = (((dClose - dLow) - (dHigh - dClose))
                / dMinRange) / 2 + 0.5; 
        }
    }

    return arrNormOnCondition;
}

// ------

void CreateSomLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strSomLagFileName, "wb", bIsPathRelative);
    F_PRINT(hFile, "%s", "X,Y");

    string strToken = GET_TOKEN(strLags, ",");
    double nSomNumOfLags = STR2NUM(strToken);

    ARRAY_REMOVE(arrSomLags, -1);
    ARRAY_REMOVE(arrColumnNumbers, -1);

    for(double i = 0; i < nSomNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrSomLags[i] = STR2NUM(strToken);

        F_PRINT(hFile, ",Y-%.0f", arrSomLags[i]); 
        arrColumnNumbers[i] = i+1;
    }
    F_PRINT(hFile, "%s", "\r\n");

    for(double i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        F_PRINT(hFile, "%.0f", i - nRemoveFirst + 1);
        for(double j = 0; j < ARRAY_SIZE(arrSomLags); j = j + 1)
        {
            F_PRINT    (hFile, ",%f", arrSomNoc[i - arrSomLags[j]]);      
        }

        F_PRINT(hFile, "%s", "\r\n");
    }

    F_CLOSE(hFile);
}


// ------

void CreateLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strLagFileName, "wb", bIsPathRelative);

    F_PRINT(hFile, "%s", "No,Clv");

    ARRAY_REMOVE(arrLags, -1);

    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);

    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrLags[i] = STR2NUM(strToken) + nOutLag;
    }

    double nFullLag;
    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        nFullLag = arrLags[i];
        F_PRINT(hFile, ",NocMa%.0f", nMa, "-%.0f", nFullLag); 
    }
    
    F_PRINT(hFile, "red,green,blue%s\r\n", "");

    double nNum;
    for(i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        nNum = i - nRemoveFirst + 1;
        F_PRINT(hFile, "%.0f", nNum, ",%f", arrNocSmooth[i]);     

        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrNocSmooth[i - arrLags[j]]);     
        }

        for(double k = 0; k < 3; k = k + 1)
        {
            F_PRINT(hFile, ",%f", arrColors[3 * (i - nRemoveFirst) + k]);
        }

        F_PRINT(hFile, "%s\r\n", "");
    }

    F_CLOSE(hFile);
}

// --------------

void NewNn(array arrLags, double dStopError, double nStopEpoch, 
    double nNeuronsLayer2)
{
    double nSkipBefore = 0;
    double nSkipAfter = 0;
    string strStartLine = "";
    string strEndLine = "";
    double bReverseArrays = 0;

    // Inputs
    array arrInputColumns = CREATE_ARRAY(0);
    array_s arrInputColumnNames = CREATE_ARRAY_S(0);
    
    // 0  - Number, 1 - Clv, our input begins at column No 2
    for(double nInputCol = 0; nInputCol < ARRAY_SIZE(arrLags); 
        nInputCol = nInputCol + 1) 
    {
        arrInputColumns[nInputCol] = nInputCol + 2;
        arrInputColumnNames[nInputCol] = 
            "Clv-" + NUM2STR(arrLags[nInputCol], "%.0f"); 
    }

    array arrOutputColumns = CREATE_ARRAY(0);
    arrOutputColumns[0] = 1;    // Noc

    array_s arrOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutputColumnNames[0] = "Noc";

    nExtractRecords = 0.7 * ARRAY_SIZE(arrClose);

    double nNeuronsLayer1 = ARRAY_SIZE(arrLags);
//    double nNeuronsLayer2 = 7;
    double nNeuronsLayer3 = 1;
    double nNeuronsLayer4 = 0;
    double nLayers = 3; 
    double nActivation = 0;
    double nAdjustRange = 1.0;

    array arrOutTabInputColumns = CREATE_ARRAY(0);
    arrOutTabInputColumns[0] = 0;    // Number

    array_s arrOutTabInputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabInputColumnNames[0] = "No";

    array arrOutTabOutputColumns = CREATE_ARRAY(0);
    // Desired output and NN output will be added to the 
    // same list, right after inputs
    arrOutTabOutputColumns[0] = ARRAY_SIZE(arrLags) + 2;    
    arrOutTabOutputColumns[1] = ARRAY_SIZE(arrLags) + 3;

    array_s arrOutTabOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabOutputColumnNames[0] = "Noc"; 
    arrOutTabOutputColumnNames[1] = "NN: Noc" ;

    CREATE_NN(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), 
        bIsPathRelative, strLagFileName, 
        bIsPathRelative, nSkipBefore, nSkipAfter, 
        strStartLine, strEndLine,
        bReverseArrays, arrInputColumns, arrInputColumnNames, 
        arrOutputColumns, arrOutputColumnNames, nExtractRecords, 
        dStopError, nStopEpoch, 
        nNeuronsLayer1, nNeuronsLayer2, nNeuronsLayer3, 
        nNeuronsLayer4, nLayers, 
        nActivation, nAdjustRange, arrOutTabInputColumns, 
        arrOutTabInputColumnNames,
        arrOutTabOutputColumns, arrOutTabOutputColumnNames);
}

// ----------------

void TeachNn()
{
    PRINT("%s\r\n", "Opening NN dialog, teaching the NN");

    double bStartLearning = 1; 
    double bResumeScript = 1;
    double bReset = 1;

    OPEN_NN_FILE(strNnFileName + NUM2STR(nCounter, "_%.0f.nn"), bIsPathRelative, 
        bStartLearning, bResumeScript, bReset);
}


// -------------

void Chart(string strForexName)
{
    string strWinNnFileName;
        
    string strImageFileName = strImagePath + strForexName + 
        NUM2STR(nImageNum, "_%.0f") + "_nn_05.png";    
            
    strXML = strXML + "\t<symbol>\r\n\t\t<symbol>\r\n";

    strXML = strXML + "NN: " + NUM2STR(nCounter, "%.0f, ")
        + "Trades: " + NUM2STR(nTradeNumber, "%.0f")
        + "(Buy: " + NUM2STR(nTradeNumberBuy, "%.0f")
        + ", Sell :" + NUM2STR(nTradeNumberSell, "%.0f)\r\n")
        + "NocInterval: " + NUM2STR(nInterval, "%.0f")
        + ", Range: " + NUM2STR(dRange, "%.3f")
        + ", Ma: " + NUM2STR(nMa, "%.0f\r\n")

        + "Lag: " + NUM2STR(nOutLag, "%.0f")
        + ", Neurons: " + NUM2STR(nNeurons, "%.0f\r\n")
        + "Stop: " + NUM2STR(dStopLoss, "%.4f")
        + ", Tp: " + NUM2STR(dTakeProfit, "%.4f")
        + NUM2STR(dStopIncrease, ", Stop increase: %f")
        + ", Buy: " + NUM2STR(dBuyLevel, "%.3f")
        + ", Sell: " + NUM2STR(dSellLevel, "%.3f\r\n")

        + "Drawdown: " + NUM2STR(dMaxDrawDown, "%.3f\r\n")

        + "Profit: " + NUM2STR(arrBalance[ARRAY_SIZE(arrBalance) - 1] - 1000, "%f")
        + " (long: " + NUM2STR(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1], "%f")
        + ", short: " + NUM2STR(arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1], "%f)\r\n");

    strXML = strXML + "\t\t</symbol>\r\n";

    strXML = strXML + "\t\t" + SAVE_CHART(400, 300, 0, strImageFileName, 
        arrBars, arrBalance, arrBalanceBuy, arrBalanceSell);
    strXML = strXML + "\t</symbol≶\r\n";
}

// ------

void ApplySom()
{
    double nSomNeurons = 400;
    double nSomMatrixWidth = 20;
    double nSomMatrixHeight = 20;
    double nSomWeights = 17;
    double nSomTotalWeights = 6800;

    array arrSomWeights = CREATE_ARRAY(0);

    arrSomWeights[0] = 0.213714;
    arrSomWeights[1] = 0.205900;
    arrSomWeights[2] = 0.193368;
    ...

    arrSomWeights[6797] = 0.285034;
    arrSomWeights[6798] = 0.537781;
    arrSomWeights[6799] = 0.801422;
    
    array arrSomColors = CREATE_ARRAY(0);
    
    arrSomColors[0] = 14;
    arrSomColors[1] = 13;
    arrSomColors[2] = 41;
    ...
    arrSomColors[397] = 2;
    arrSomColors[398] = 0;
    arrSomColors[399] = 0;

    // Confine it to 0-255 range
    double dMaxColor = ARRAY_MAX(arrSomColors, -1, -1);
    arrSomColors = 255 * arrSomColors    / dMaxColor;
                        
    // We will read patterns from the lag file, 
    // one line is one pattern
    array arrPattern = CREATE_ARRAY(0);
    
    double hFile = F_OPEN(strSomLagFileName, "rb", 1);
        
    // Skip the first line containing headers
    string strPattern = F_GETS(hFile); 

    // We are going to cheat here. Technically, we need to 
    // normalize data, but we know, that lag file is already
    // normalized.
    
    string strPattern;
    string strToken;
    
    for(double nPattern = 0; nPattern < 100000; nPattern = nPattern + 1)
    {
        if(F_EOF(hFile) == 1)
        {
            break nPattern;
        }

        strPattern = F_GETS(hFile); 
        
        // The last CR-LF, empty string
        if(STR_LEN(strPattern) < 10)
        {
            break nPattern;
        }
        
        if(nPattern % 100 == 0)
        {
            PRINT("Pattern: %.0f\r\n", nPattern);
        }
        
        // Skip first column containing column number 
        strToken = GET_TOKEN(strPattern, ",");

        ARRAY_REMOVE(arrPattern, -1);

        for(double nLag = 0; nLag < nSomNumOfLags; nLag = nLag + 1)
        {
            strToken = GET_TOKEN(strPattern, ",");
            double dValue = STR2NUM(strToken);
            arrPattern[nLag] = dValue;
        }

        // Pattern loaded, now process
        
        double nWinner = FindWinner();
        
        double nRow = FLOOR(nWinner / nSomMatrixWidth);
        double nColumn = FLOOR(nWinner % nSomMatrixWidth);
        
        arrColors[3 * nPattern] = 128 * nColumn / nSomMatrixWidth;
        arrColors[3 * nPattern + 1] = 128 * nRow / nSomMatrixHeight;
        arrColors[3 * nPattern + 2] = arrSomColors[nWinner];
    }
    
    PRINT("APPLY_SOM completed %.0f patterns\r\n", nPattern);
}

// ------

double FindWinner()
{
    double nWinner = 0;
    double dMinDistance = 999999;
 
    for(double nNeuron = 0; nNeuron < nSomNeurons; nNeuron = nNeuron + 1)
    {
        double dDistance = CalculateDistance();

        if(dDistance < dMinDistance)
        {
            dMinDistance = dDistance;
            nWinner = nNeuron;
        }
    }

  return nWinner;
}

// ------

double CalculateDistance()
{
    double dDistance = 0;
  
    for(double nWeightIdx = 0; nWeightIdx < nSomWeights; nWeightIdx = nWeightIdx + 1)
  {
        double nWeight = nNeuron * nSomWeights + nWeightIdx;
        dDistance = dDistance + (arrPattern[nWeightIdx] - arrSomWeights[nWeight]) ^ 2;
  }

  return dDistance;
}

Porting Self-Organizing Map forex trading system based to MT

The final step is to port our script to MetaTrader, so that it can be used for real trading. We are going to use the same expert as in the "FFBP only" example above, however, the indicator will contain both FFBP and SOM code.

Note, that in addition, mylib.mq4 is used, its code is the same as in "FFBP only" example.

_Forex_SOM_Expert.mq4, fragment

...
double dNoc = iCustom(NULL, 0, "_Forex_SOM_Ind", 1, 1);
double dNocPrev = iCustom(NULL, 0, "_Forex_SOM_Ind", 1, 2);
...

The indicator's code was created based on "FFBP only" example, by adding the SOM code, ported from som_12.tsc:

_Forex_SOM_Ind.mq4, fragment

#property indicator_separate_window
#property indicator_buffers 5
#property indicator_color1 Red
#property indicator_color2 Yellow
#property indicator_minimum 0
#property indicator_maximum 1

// indicator parameters
// To do: read from file

int nNocInterval = 12;
double dNocRange = 0.004;
int nNocMa = 3;
int nOutLag = 2;

int nLayers = 3;
int arrNeurons[3] = { 17, 3, 1 };
int nNumOfLags = 17;
int arrLags[17] = 
    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };

double arrWeights_0[306];
double arrWeights_1[54];
double arrWeights_2[4];

double arrSomColors[400];
double arrSomWeights[6800];
double dMaxColor;
double nSomNeurons;
double nSomMatrixWidth;
double nSomMatrixHeight;
double nSomWeights;
double nSomTotalWeights;

double arrOutput_0[17];
double arrOutput_1[3];
double dNnOutput;

double arrPattern[21];

int nRemoveFirst = 200;
double dE = 2.7182818;

// indicator buffers
double arrNocBuffer[];
double arrNnBuffer[];
double arrRed[];
double arrGreen[];
double arrBlue[];

int nExtCountedBars = 0;

////////////////////////
int init()
{
    // Layer: 0
    arrWeights_0[0] = 877.529097;
    arrWeights_0[1] = -979.236938;
    arrWeights_0[2] = 397.873154;
    ...
    arrWeights_1[51] = -31.510057;
    arrWeights_1[52] = 7.666437;
    arrWeights_1[53] = 58.141611;

    // Layer: 2
    arrWeights_2[0] = 5.346615;
    arrWeights_2[1] = 5.275201;
    arrWeights_2[2] = 6.543355;
    arrWeights_2[3] = 8.811740;

   // ---
    nSomNeurons = 400;
    nSomMatrixWidth = 20;
    nSomMatrixHeight = 20;
    nSomWeights = 17;
    nSomTotalWeights = 6800;
    
    arrSomWeights[0] = 0.213714;
    arrSomWeights[1] = 0.205900;
    arrSomWeights[2] = 0.193368;
    ...
    arrSomWeights[6797] = 0.285034;
    arrSomWeights[6798] = 0.537781;
    arrSomWeights[6799] = 0.801422;
    
    arrSomColors[0] = 14;
    arrSomColors[1] = 13;
    arrSomColors[2] = 41;
    ...
    arrSomColors[397] = 2;
    arrSomColors[398] = 0;
    arrSomColors[399] = 0;

    // Confine it to 0-255 range
    dMaxColor = arrSomColors[ArrayMaximum(arrSomColors)]; 
    for(int i = 0; i < ArraySize(arrSomColors); i++)
        arrSomColors[i] = 255 * arrSomColors[i] / dMaxColor;

   // ---

    // drawing settings
    SetIndexStyle(0, DRAW_LINE);
    SetIndexShift(0, 0);
    SetIndexEmptyValue(0, 0.0);
        
    SetIndexStyle(1, DRAW_LINE);
    SetIndexShift(1, 0);
    SetIndexEmptyValue(1, 0.0);

    SetIndexStyle(2, DRAW_NONE);
    SetIndexShift(2, 0);
    SetIndexEmptyValue(2, 0.0);
        
    SetIndexStyle(3, DRAW_NONE);
    SetIndexShift(3, 0);
    SetIndexEmptyValue(3, 0.0);

    SetIndexStyle(4, DRAW_NONE);
    SetIndexShift(4, 0);
    SetIndexEmptyValue(4, 0.0);

    IndicatorDigits(4);
        
    string strIndicatorShortName = "forex_nn";
    IndicatorShortName(strIndicatorShortName);

    // indicator buffers mapping
    SetIndexBuffer(0, arrNocBuffer);
    SetIndexBuffer(1, arrNnBuffer);    
    SetIndexBuffer(2, arrRed);    
    SetIndexBuffer(3, arrGreen);    
    SetIndexBuffer(4, arrBlue);    

    return(0);
}
////////////////////
int start()
{
    nExtCountedBars = IndicatorCounted();
    if(nExtCountedBars < 0) 
        return(-1);

    // last counted bar will be recounted
    if(nExtCountedBars > 0) 
        nExtCountedBars--;
        
    Noc();    
    ApplySom();    
    ApplyNn();  

    return(0);
}
///////////////////
// For the selected period:
// CLV = ((Close - Low) - (High - Close)) / (High - Low) 
void Noc()
{
    int nPos = Bars - nExtCountedBars;

    while(nPos > 0)
    {
        if(nPos > Bars - nRemoveFirst)
        {
            arrNocBuffer[nPos] = 0.5;
            nPos--;
            continue;
        }

        double dClose = Close[nPos];
        double dLow = Low[ArrayMinimum(Low, nNocInterval, nPos)]; 
        double dHigh = High[ArrayMaximum(High, nNocInterval, nPos)]; 

        arrNocBuffer[nPos] = 0.1;

        if(dHigh - dLow > dNocRange)
            arrNocBuffer[nPos] = (((dClose - dLow) - (dHigh - dClose)) 
                / (dHigh - dLow)) / 2 + 0.5; 
        else
            arrNocBuffer[nPos] = (((dClose - dLow) - (dHigh - dClose)) 
                / dNocRange) / 2 + 0.5; 

        nPos--;
    }
    
    if(nNocMa > 1)
        Ema(arrNocBuffer);
}
///////////////////
void Ema(double& arr[])
{
    double dPr = 2.0 / (nNocMa + 1);

    int nPos = Bars - nExtCountedBars;
    
    while(nPos > 0)
    {
        arrNocBuffer[nPos] = arr[nPos] * dPr + arrNocBuffer[nPos + 1] * (1 - dPr);
        nPos--;
    }
}
///////////////////
void ApplyNn()
{
//int hFile = FileOpen("test.txt", FILE_CSV|FILE_WRITE, ";");

    int nPos = Bars - nExtCountedBars;// - 2;

    while(nPos > 0)
    {
        if(nPos > Bars - nRemoveFirst)
        {
            arrNnBuffer[nPos] = 0.5;
            nPos--;
            continue;
        }

        arrNnBuffer[nPos] = 0.5;
        
        // First, add NOC
        for(int nLagNo = 0; nLagNo < nNumOfLags; nLagNo++)
            arrPattern[nLagNo] = arrNocBuffer[nPos + arrLags[nLagNo] + nOutLag];
            
        // Then, add SOM output
        arrPattern[nLagNo] = arrRed[nPos];
        nLagNo++;
        arrPattern[nLagNo] = arrGreen[nPos];
        nLagNo++;
        arrPattern[nLagNo] = arrBlue[nPos];
            
        for(int nLayer = 0; nLayer < nLayers; nLayer++)
        {
            // Absolute index of the weight in an array
            int nWeightIdx = 0;

            for(int nNeuron = 0; nNeuron < arrNeurons[nLayer]; nNeuron++)
            {
                double dLinearCombiner = 0;
    
                int nInputs = arrNeurons[nLayer];
                if(nLayer != 0)
                    nInputs = arrNeurons[nLayer - 1];
                
                for(int nInput = 0; nInput < nInputs; nInput++)
                {
                    double dInput;
                    double dWeight;
                    switch(nLayer)
                    { 
                        case 0:
                        {
                            dInput = arrPattern[nInput];
                            dWeight = arrWeights_0[nWeightIdx];
                        }
                        break;
                        
                        case 1:
                        {
                            dInput = arrOutput_0[nInput];
                            dWeight = arrWeights_1[nWeightIdx];
                        }
                        break;
                        
                        default:
                        {
                            dInput = arrOutput_1[nInput];
                            dWeight = arrWeights_2[nWeightIdx];
                        }
                        break;
                    }

                    dLinearCombiner += dWeight * dInput;
                    nWeightIdx++;
                }        

                switch(nLayer)
                {
                    case 0:
                        dWeight = arrWeights_0[nWeightIdx]; break;
                    case 1:
                        dWeight = arrWeights_1[nWeightIdx]; break;
                    default: 
                        dWeight = arrWeights_2[nWeightIdx]; break;
                }

                dLinearCombiner += (-1) * dWeight;
                nWeightIdx++;

                double dActivation = Activation(dLinearCombiner / (nInputs + 1), 0);

                switch(nLayer)
                {
                    case 0:
                        arrOutput_0[nNeuron] = dActivation; break;
                    case 1:
                        arrOutput_1[nNeuron] = dActivation; break;
                    default: 
                        dNnOutput = dActivation; break;    // 1 neuron in the last layer
                }

            }    // for all neurons

        }    // for all layers

        arrNnBuffer[nPos] = dNnOutput;

        nPos--;
            
    }    // for all patterns
    
//FileClose(hFile);        
}
///////////////////

double Activation(double u, int nType)
{
    double dPow = MathPow(dE, 2 * u / 2);
    double dEx = (dPow - 1) / (dPow + 1);
    if(dEx == 1 || dEx == -1)
        dEx = dEx * 0.999999999;
    
    // 0 - sigmoid, 1 - tangent
    if(nType == 0)
        return((dEx + 1) / 2);

    return(dEx);
}

///////////////////
void ApplySom()
{
    int nPos = Bars - nExtCountedBars;// - 2;

    while(nPos > 0)
    {
        if(nPos > Bars - nRemoveFirst)
        {
            arrRed[nPos] = 0;
            arrGreen[nPos] = 0;
            arrBlue[nPos] = 0;
            nPos--;
            continue;
        }

        arrRed[nPos] = 0;
        arrGreen[nPos] = 0;
        arrBlue[nPos] = 0;
        
        // First, add NOC
        // Note, that coincidentally, NOC and SOM have same set of lags
        for(int nLagNo = 0; nLagNo < nNumOfLags; nLagNo++)
            arrPattern[nLagNo] = arrNocBuffer[nPos + arrLags[nLagNo] + nOutLag];

        int nWinner = FindWinner();
        
        int nRow = nWinner / nSomMatrixWidth;
        int nColumn = nWinner - nRow * nSomMatrixWidth;
        
        arrRed[nPos] = 128 * nColumn / nSomMatrixWidth;
        arrGreen[nPos] = 128 * nRow / nSomMatrixHeight;
        arrBlue[nPos] = arrSomColors[nWinner];

        nPos--;
            
    }    // for all patterns
    
//FileClose(hFile);        
}
///////////////////

int FindWinner()
{
    double nWinner = 0;
    double dMinDistance = 999999;
 
    for(int nNeuron = 0; nNeuron < nSomNeurons; nNeuron++)
    {
        double dDistance = CalculateDistance(nNeuron);

        if(dDistance < dMinDistance)
        {
            dMinDistance = dDistance;
            nWinner = nNeuron;
        }
    }

  return(nWinner);
}

// ------

double CalculateDistance(int nNeuron)
{
    double dDistance = 0;
  
    for(int nWeightIdx = 0; nWeightIdx < nSomWeights; nWeightIdx++)
  {
        int nWeight = nNeuron * nSomWeights + nWeightIdx;
        dDistance = dDistance + (arrPattern[nWeightIdx] - arrSomWeights[nWeight]) *
         (arrPattern[nWeightIdx] - arrSomWeights[nWeight]);
  }

  return(dDistance);
}


Final notes

That is it. You can now create Cortex Neural Network Software script, that takes SOM output as its inputs, among other inputs, optimize it to do trading, and to port it to the trading platform of your choice.

Note, that this is not your only option - you can use this approach, for example, to create FFBP NN that uses more than one indicator plus another FFBP NN output as its input, and so on.







(C) snowcron.com, all rights reserved

Please read the disclaimer