SnowCron

.COM


SnowCron.com







In this article: an example of using of our Neural Networks Software to create a complete neural network trading system.

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

Using Neural Networks to create FOREX Trading Strategy

In this free online tutorial you will find the "full cycle" of using neural networks (Cortex Neural Networks Software) for Forex trading (or stock market trading, the idea is the same).

You will learn how to choose inputs for the artificial neural networks, and how to decide what to use as the output.

You will find an example of a ready to use script that allows to perform neural networks optimization of both the structure of Neural Network (number of neurons) and the forex trading system (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, MetaQuotes or MetaTrader. How to port the FOREX trading system 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 Neural Networks Software comes with the easy to use feature that allows you to easily port the resulting (trained) Neural Network 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 Neural Networks Software, 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 as a forex trading strategy "as is". The idea of this text is to teach you to create NN-based trading systems 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 uses 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, forex trading system, 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 archive.


Pitfalls of FOREX BUY / SELL Signals: What is wrong with "simple" examples?

In the Cortex Neural Networks Software user's guide we used a simple example of an aftifficial Neural Network, predicting the price of GENZ stock. To find out what is wrong with this approach, let's do the same "simple" example, using MSFT.TXT, instead of the GENZ.TXT (use 800 records in the learning set, as MSFT.TXT is a little bit shorter, then GENZ.TXT).

GENZ MSFT

It just wouldn't work! Why?

The reason will become evident, if you ask yourself: "What is the reason neural network forecasting of future values can be done on the first place?"

The answer is: it is learning to do what is called neural networks pattern recognition, to recognize patterns, and if there is a hidden logic in these patterns, then even a new pattern (with the same logic) will be recognized.

That's a trick - "with the same logic". There is not even one, but three problems here.

First of all, if you look at the Microsoft's stock price, you will notice, that it was going down in the "learning" part of our data, and sideways - in the "testing" part. So it is possible, that the logic had changed.

Second, and even more important - WHAT IS THE PATTERN? You see, if we teached the neural network in the range 10 - 100, and then presented it with something in the 1 to 3 range - they are different patterns! 10, 20, 30 and 1, 2, 3 look similar to the human because - BECAUSE - we have this ability to divide by ten, when presented with numbers ending with zero. It is what is called a pre-processing of the data, and by default, the NN can not do it.

Can we teach it? Of course. What is it EXACTLY we need to teach it?

This is the third, and the most important one. We do not need the price prediction! We do not care! What we need is FOREX buy sell signals.

Now, wait a minute! We need a) to have our input (both learning and testing) in the same range, and we need b) to be able to make trading decisions based on it? Isn't it what we call an indicator? Bingo?

So, that's what we are going to do - we will build an indicator, to feed it to the NN as an input, and we will try to get a prediction of the indicator value, not the worthless stock price!

In our first example, we will load stock quotes from the disk, open the Neural Network file and start the learning - all in an automated mode.

Create a new script file (or open the one that came with the Cortex Neural Networks Software archive) and call it stocks_nn.tsc.

First of all, we need to download the price values from the MSFT.TXT file. We are going to use the CLV indicator (see below), but to calculate it, we need split-adjusted values for High and Low, not just for close. Here is how to get them.

stocks_nn.tsc, part 1

void main()
{
    string strStockPath = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\stocks\\msft.txt";
    double bIsPathRelative = 0;

    array arrDate = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrC = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    // Loading data
    TABLE_LOADER(strStockPath, bIsPathRelative, 1, "", 1, 
        "<!-- chart1", 0, arrDate, 2, arrHigh, 
        3, arrLow, 4, arrC, 6, arrClose);

    // Adjust for splits
    double dSplit;
    for(double i = 0; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        dSplit = arrC[i] / arrClose[i];
        arrHigh[i] = arrHigh[i] / dSplit;
        arrLow[i] = arrLow[i] / dSplit;
    }

The first line assigns the path to the strStockPath variable, of course, you will have to edit it, if your data file is located in the different directory.

In the second line we specify, that this path is not relative (the "relative" to the location of Cortex.exe file).

The TABLE_LOADER receives the path, the empty string for the "start line", 1 - to skip the first line (column names), part of the file's footer line (the last line in MSFT.TXT does not contain data), it is also instructed to load the column number 0 (and call it arrDate), 2 (arrHigh), 3 (arrLow), 4 (arrC) and 6 (arrClose).
For a full description of TABLE_LOADER, see the SLANG reference guide.

Then we calculate split, by dividing the Adjusted Close by Close, and use this value to adjust Low and High.

The MSFT.TXT file contains newest data FIRST, while we want them LAST.

Next, we need to create an indicator. Let's say, it is going to be a Close Location Value indicator, though in the "real life" I would probably use more than one indicator as the NN input.

The Close Location Value indicator is calculated like

CLV = ((Close - Low) - (High - Close)) / (High - Low), where Close, Low and High are for the interval, not necessarily for a single bar. Note, that we want it in the 0 - 1 range, to make it easier to normalize to our NN's range (which is, again, 0-1).

stocks_nn.tsc, part 3

    array arrClv = CREATE_ARRAY(0);
    double nInterval = 7;

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

    arrPeriodLow = MVMIN(arrLow, nInterval);
    arrPeriodHigh = MVMAX(arrHigh, nInterval);

    nArraySize = ARRAY_SIZE(arrClose);
                        
    for(i = 0; i < nInterval; i = i + 1)
    {
        arrClv[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];

        // / 2 + 1 to confine to 0...1 instead of -1...1
        arrClv[i] = (((dClose - dLow) - (dHigh - dClose))
            / (dHigh - dLow)) / 2 + 0.5; 
    }

Next, we need to create a lag file. Let's use lags equal to 1, 2... 9 (For details on file functions, see the SLANG reference guide). Note, that the Cortex's NN dialog can produce simple lags automatically (you can use a "Generate lag" button). But later in this text, we are going to work with complex lags (which means, they are not 1, 2, 3... but 1, 3, 64... whatever), so we need to create the code that can handle this task in a more flexible way.

stocks_nn.tsc, part 4

    string strLagFileName = 
        "c:\S_Projects\CortexPro\data\samples\stocks\msft_ind.lgg";
    double hFile = F_OPEN(strLagFileName, "wb");

    F_PRINT(hFile, "%s\r\n", 
        "Number,Clv,Clv-1,Clv-2,Clv-3,Clv-4,Clv-5,
            Clv-6,Clv-7,Clv-8,Clv-9"); 

    double nMaxLag = 9 + nInterval;

    for(i = nMaxLag; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        F_PRINT(hFile, "%.0f", i - nMaxLag + 1);
        for(double j = 0; j < nMaxLag + 1; j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrClv[i - j]);     
        }
        F_PRINT(hFile, "%s", "\r\n");
    }

    F_CLOSE(hFile);

Having the lag file, we are ready to create our first neural network. This function takes a lot of parameters, so be carefull. However, the code is really simple.

By the way, most of this code can be removed, if you think you can handle numbers, instead of meaningfull names in your code, however, that would be a very bad coding practice.

stocks_nn.tsc, part 5

    double nSkipBefore = 0;
    double nSkipAfter = 0;
    string strStartLine = "";
    string strEndLine = "";

    // We already have them reversed
    double bReverseArrays = 0;        
    
    array arrColumns= CREATE_ARRAY(0);
    array_s arrColumnNames = CREATE_ARRAY_S(0); 
    array arrLags = CREATE_ARRAY(0);

    // Inputs
    arrLags[0] = 0;
    arrColumns[0] = 6;
    arrColumnNames[0] = "Adj. Close*"; 

    // Outputs
    arrColumns[1] = 6;
    arrColumnNames[1] = "Adj. Close*"; 

    for(i = 0; i < 10; i = i + 1)
    {
        arrLags[i] = i;
    }

    array arrInputColumns = CREATE_ARRAY(0);
    // 0  - Number, 1 - Clv, our input begins at column No 2
    arrInputColumns[0] = 2;
    arrInputColumns[1] = 3;
    arrInputColumns[2] = 4;
    arrInputColumns[3] = 5;
    arrInputColumns[4] = 6;
    arrInputColumns[5] = 7;
    arrInputColumns[6] = 8;
    arrInputColumns[7] = 9;
    arrInputColumns[8] = 10;

    array_s arrInputColumnNames = CREATE_ARRAY_S(0);
    arrInputColumnNames[0] = "Clv-1"; 
    arrInputColumnNames[1] = "Clv-2"; 
    arrInputColumnNames[2] = "Clv-3"; 
    arrInputColumnNames[3] = "Clv-4"; 
    arrInputColumnNames[4] = "Clv-5"; 
    arrInputColumnNames[5] = "Clv-6"; 
    arrInputColumnNames[6] = "Clv-7"; 
    arrInputColumnNames[7] = "Clv-8"; 
    arrInputColumnNames[8] = "Clv-9";
 
    array arrOutputColumns = CREATE_ARRAY(0);
    arrOutputColumns[0] = 1;    // Clv

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

    double nExtractRecords = 800;
    double dStopError = 0;
    double nStopEpoch = 0; 
    double nNeuronsLayer1 = 9;
    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);
    arrOutTabOutputColumns[0] = 1;
    // Outputs will be added to the same list, right after inputs
    arrOutTabOutputColumns[1] = 10;

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

    string strNnFileName = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\nn\\msft_ind.nn";

    CREATE_NN(strNnFileName, 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);

Now, after we have a neural network and the lagged file with data, we need to teach the network. The lag file (msft_ind.lgg) has 1074 records, so it is reasonable to use 800 as a learning set, and the remaining 274 as a testing set.

You can, of course, open a network file and to click the "Run" button on the "Learning" tab. But as this is an introduction to advanced Cortex Neural Networks Software programming, let's use SLANG built_in scripting language instead.

The following code brings up the modal dialog with ann NN settings. Note, that if you want to have a privilege of clicking the "Run" button, you need to change the

stocks_nn.tsc, part 6

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

    OPEN_NN_FILE(strNnFileName, bIsPathRelative, 
        bStartLearning, bResumeScript, bReset);

The bStartLearning can be 0, in which case the dialog will wait for your input, or 1, then the learning will begin aytomatically.

The bResumeScript, if equals 1, will resume the script, if you close the dialog by clicking the OK button.

The bReset is used to reset the network before the learning begins.


Run the script, and wait for the epoch counter to exceed 1000, then click "Stop". Go to the "Apply" tab, and click "Apply". This will run the entire data set (both learning and testing) through the NN, and create the .APL file, containing both original input-output, and the NN-generated prediction, this way you can easily plot them and compate against each other.

Go to the "Output" tab, select msft_ind.apl file, click "Browse file", "Select fields", then select the "No" in the left list box, and (by holding down the CTRL key while selecting with the mouse) Clv and NN:Clv in the right list box. Click "Chart" to see how good our prediction is. Well... It is more or less good, from what we can say by looking at it. Still, nothing extraordinary...

This was just an example of what you can do with SLANG scripting, and how to automate Cortex's routine tasks. However, until now, we did nothing you couldn't do "by hand". Well... almost nothing, because if you want to create a custom lag file, with, say, Clv-100, Clv-50, Clv-25... columns, then you will have to use SLANG (or Excel...), because you cannot do in in Cortex without scripting.


FOREX Trading Strategy: what to optimize?

Here is our next problem. Do we need a good-looking prediction, or do we need the one we can use to trade with profit? The question seems odd, but just think about it for a moment. Let's say we have a VERY good 1-hour prediction. 95% accurate. Still, how far can the price go in one hour? Not too far, I am afraid. Compare it to the situation, when you have a rather inaccurate 10-hours prediction. Will it be better?

To answer this question, we need to actually trade, a simple comparison of the mean errors produced by the two NNs will not help.

The second part (of the same problem) is in the way we define a "good prediction". Let's say we have a network, that produces the prediction, which is 75% accurate. Compare it to the NN, that is producing 100% accurate prediction. The last one is better. Now, DIVIDE the output (prediction) of the 100% accurate NN by 10. We will have a VERY inaccurate network, as its signal is nowhere near the signal we used as a "desired output". And yet, it can be used same way we used 100% accurate NN, all we have to do is to multiply it to 10!

See, the NN is created, by tuning the mean quadratic error, and not the correlation, so, at least in theory, a better NN can show poor results, when used for the actual stock / Forex trading.

To solve this problem, we need to test our NNs using trading, and to use results of this trading (profit and drawdowns) to decide, if this NN is better than the other one.

Let's do it. Let's create a program, that can be used to fine-tune NN, and this time, by fine-tuning, we will mean trading results.


Neural Network Trading: Few short notes

First of all, in our example above, the "automatic" learning will never stop, because we haven't specified any stop criteria. In the dialog, or in the CREATE_NN function, you can provide the min. error (when the NN reaches it, it stops and, if bResumeScript is set to 1, the dialog will close and the script will resume). Also yo can provide the maximum number of epochs, or both. I am not using it in the example below, at least not always, because I am planning to watch the learning and to click STOP when I think the NN is ready. If you want to do it in fully automatic mode, pay attention to these parameters.

Second. One of the ways to make a network smaller, faster and more accurate, is to begin with the small network, and increase it's size, neuron by neuron. Obwiously, the number of the input neurons is determined by the number of input data columns (but we can vary them, too), and the number of output neurons should be equal to the number of output data columns (usually one, but not necessarily). This means we need to optimize the number of neurons in the hidden layer(s).

Also, as I have mentioned, we don't really know which data to use. Will Clv-15 (15 days delayed) increase the accuracy of our prediction? Do we need Clv-256? Will it be better to use both of them in the same NN, or will adding Clv-256 ruin our performance?

Using nested cycles to try different input parameters, you can:

  1. Create the NN, same way we did it for the stock data (let me repeate, for the NN, there is no difference between stocks and FOREX, it just happened that I have couple of high quality data files for FOREX that I want to process, while writing this text).
  2. Try different combinations of lags.
  3. Try different number of neurons in the hidden layer.
  4. ... and different combinations of different indicators...
  5. ...and so on.

However, if you try all possible combinations of all possible parameters, you will NEVER get your results, no matter how fast your computer is. Below, we will use couple of tricks to reduce calculations to a bare minimum.

By the way, it may seem, that if you start from one hidden neuron, then increase it to 2, 3 and so on, and at some point the error (quality of the prediction) or the profit (if you test the NN by trading using it) will begin to go down, then you have your winner. Unfortunately, I cannot prove, that after the first "performance peak" there can be no second one. It means, that the error may go like 100, 30, 20, 40, 50 (it was just at its minimum, right?) and then 30, 20, 10, 15, ... (the second minimum). We just have to test all reasonable numbers.

Third. Optimization is a two-edged sword. If you over-optimize your code, it may not work outside the data you used to fine-tune it. I will do my best to avoid this pitfall. If you want to do additional optimizations to your code or NN, I advise you to do a research in the Internet, to learn more about hidden problems of this approach. ALso, I am going to pay some attention to the smoothness of the profit curve. The profit that looks like 0, -500, 1000, -100, 10000 may be great, but the profit 0, 100, 200, 300, 400... is better, as it is less risky. We may talk about it later.

Finally, for this example we are going to use FOREX, rather than stock prices. From the point of view of the NN there is no difference, and from my point - Forex is much more fun to trade. If you prefer stocks, the code can easily be modified.


A FOREX Trading Strategy to play with

First of all, let's create a prototype of our code, one that can easily be optimized in future. It is going to be a trading system, that uses a Neural Network to trade and produces a chart (profit against trade number). It will also calculate drawdown, as a measure of robustness of our trading system.

forex_nn_01.tsc, part 1

void main()
{
    OUT_CLEANUP();

    // ***** Loading data
    string strDataFileName = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\forex\\eurusd_h1.txt";
    double bIsPathRelative = 0;

    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);

    double nExtractRecords = 0.8 * ARRAY_SIZE(arrClose);

    double nInterval = 7;
    array arrClv = CreateClv(nInterval);

    array arrLags = CREATE_ARRAY(0);
    arrLags[0] = 0; arrLags[1] = 1; arrLags[2] = 2; 
    arrLags[3] = 3; arrLags[4] = 4; arrLags[5] = 5; 
    arrLags[6] = 6; arrLags[7] = 7; arrLags[8] = 8; 
    arrLags[9] = 9;

    double nMaxInterval = nInterval;
    double nMaxLag = arrLags[ARRAY_SIZE(arrLags) - 1];
    double nRemoveFirst = nMaxLag + nMaxInterval;

    string strLagFileName = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\forex\\eurusd_h1.lgg";
    CreateLagFile(strLagFileName, arrLags, nRemoveFirst);

    double dStopError = 0;
    double nStopEpoch = 0; 
    double nNeuronsLayer2 = 7;
    string strNnFileName = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\nn\\eurusd_h1.nn";
    NewNn(arrLags, dStopError, nStopEpoch, nNeuronsLayer2, 
        strNnFileName);

    TeachNn();

    double dStopLoss = 0.005;    // 50 points
    double dBuyLevel = 0.3;
    double dSellLevel = 0.7;
    TestNet();

    string strForexName = "EURUSD_H1";
    Chart(strForexName);

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

The main difference here is that we use functions, instead of placing all the code in the main block of the program. This way it is much easier to manage.

Second, we have a TestNet function. I am using a very simple algorithm of trading . The CLV indicator is confined to 0 - 1 interval (our version of CLV is), so when the indicator crosses up the dBuyLevel (see code above), I am buying, when it is crossing down the dSellLevel, I am selling.

Obviously, it is not the best trading strategy, but it will do for our purpose (just for now). If you want to improve it, here are some pointers. First, you may want to have a system, that is not ALWAYS in the market. Second, you may want to use more than one indicator as inputs, and maybe, more than one NN, so that the trading decision is made based on few predicted indicators. We will add some improvements to the trading algorithm later.

We use some standard assumptions of the FOREX trading: spread is 5 points, leverade is 100, min. lot is $100 (mini-FOREX).

Let's take a look at our "trading" system. Once again, it is an oversimplified one. An important note: the TestNn() is called last, and it has access to all variables that were created to that point. So if you see a variable that I am using, without initializing it, it probably means that it was initialized in NewNn(), TeachNn() or some other function that was called prior to TestNn().

To make things easier, comments are placed in the code.

forex_nn_01.tsc, part 2

void TestNet()
{
    PRINT("%s\r\n", "Testing the NN");
    
    // Array to hold the balance (total amount of money)
    // that we have after each trade. The initial amount 
    // is $1000
    array arrBalance = CREATE_ARRAY(0);
    arrBalance[0] = 1000;

    // Array to keep bar number, at which the trade happened
    array arrBars = CREATE_ARRAY(0);
    arrBars[0] = 0;

    // We use so-called mini-forex, that is supported by
    // most of brockers. A minimum lot size is $100
    double dLotSize = 100;    // 0.1 lot
    
    // Opened trade. -1 buy, 1 sell, 0 - none. In this simple
    // system, we can only have one opened position in a time
    double nType = 0;            

    // The result of NN calculations
    array arrNn = CREATE_ARRAY(0);

    // Open the NN, apply it to the lag file (created above, in
    // main() function) 
    double hNN = OPEN_NN(strNnFileName, bIsPathRelative);
    APPLY_NN(hNN, nExtractRecords, 1.0, 1, arrClv, arrLags, 1, arrNn);
    CLOSE_NN(hNN);

    // We are going to keep log of all our trades. I am using
    // a simple text file, that is supposed to look nice when
    // opened in Windows Notepad. If you need, change number
    // of tabs, spaces and so on, to make columns alined in your
    // editor of a choice, or save the file with HTML decorations,
    // as a table.
    string strReportFileName = 
        "c:\\s_projects\\CortexPro\\data\\stocks_nn\\report.txt";
    
    // A header to place in the REPORT.TXT    
    string str = 
        "Number  Operation  Price  Closed  ClosePrice  Total";

    double hFile = F_OPEN(strReportFileName, "wb");
    F_PRINT(hFile, "%s\r\n", str);

    // Initial values of constants we use
    double dSpread = 0.0005;
    double bStop;
    double dMaxDrawDown = 0;

    // Used to calculate drawdown
    double dCurrentMax = arrBalance[0];

    // Number of current trades, at the end, it holds the
    // total number of trades
    double nTradeNumber = 0;

    // Price at which the trade was opened. When we close the 
    // trade, it is used to calculate the profit
    double dOpenPrice;

    // Price at which stop loss will work.
    double dStop;

    // for all testing data, except for the first
    // few records, where the indicator is not
    // defined
    for(double nBar = nRemoveFirst + 1; 
        nBar < ARRAY_SIZE(arrClose); nBar = nBar + 1)
    {
        if(nType != 0) // If we have an open trade
        {
            // Will become 1 if stop loss have been fired
            bStop = 0;
            double dClosedAt;    // Execution price
            
            // If BUY and stop loss reached
            if(nType == -1 && arrLow[nBar] <= dStop)
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] 
                    + 100 * (arrLow [nBar] - dOpenPrice) * dLotSize;
                bStop = 1;
                dClosedAt = arrLow[nBar];
            }
            else
            {
                if(nType == 1 && arrHigh[nBar] >= dStop - dSpread)
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] = 
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (dOpenPrice - arrHigh[nBar] - dSpread) 
                        * dLotSize;
                    bStop = 1;
                    dClosedAt = arrHigh[nBar];
                }
            }

            // If stop loss have been fired
            if(bStop == 1)
            {
                nType = 0;
                arrBars[ARRAY_SIZE(arrBars)] = nBar;
                
                F_PRINT(hFile, "Stop    %f", dClosedAt, "    %f\r\n", 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1]);
            }
        }

        // A drawdown is calculated in relation to the price
        // maximum that (so far) was reached.
        double dDrawDown = (dCurrentMax - 
            arrBalance[ARRAY_SIZE(arrBalance) - 1]) / dCurrentMax;
        dMaxDrawDown = MAX(dMaxDrawDown, dDrawDown);
        dCurrentMax = MAX(dCurrentMax, 
            arrBalance[ARRAY_SIZE(arrBalance) - 1]);

        // This code is not required, but will increase the
        // speed dramatically, by ignoring "bad" trading systems
        // In future, we may use dMaxDrawDown here. 
        if(arrBalance[ARRAY_SIZE(arrBalance) - 1] < 500)
        {
            break nBar;
        }

        // If we have met the "buy" conditions, and we are
        // not in a long position yet.
        if(nType != -1 && 
            arrNn[nBar - 1] <= dBuyLevel && 
            arrNn[nBar] >= dBuyLevel)
        {
            if(nType == 1)    // Close short positions, if any
            {
                arrBalance[ARRAY_SIZE(arrBalance)] = 
                    arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                    100 * (dOpenPrice - arrHigh[nBar] - dSpread) 
                    * dLotSize;
                arrBars[ARRAY_SIZE(arrBars)] = nBar;
                F_PRINT(hFile, "Close    %f", arrHigh[nBar], 
                    "    %f\r\n", arrBalance[ARRAY_SIZE(arrBalance) - 1]);
            }

            // Buy long
            dOpenPrice = arrHigh[nBar];
            dStop = dOpenPrice - dStopLoss;
            nType = -1;
            
            nTradeNumber = nTradeNumber + 1;

            F_PRINT(hFile, "%.0f    ", nTradeNumber, 
                "Buy        %f    ", arrHigh[nBar]);
        }
        else    // If we have met "sell" conditions, and we are
                // not in short position yet. 
        {
            if(nType != 1 && 
                arrNn[nBar - 1] >= dSellLevel && 
                arrNn[nBar] <= dSellLevel)
            {
                if(nType == -1)    // Close long positions, if any
                {
                    arrBalance[ARRAY_SIZE(arrBalance)] =
                        arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                        100 * (arrLow[nBar] - dOpenPrice) * dLotSize;
                    arrBars[ARRAY_SIZE(arrBars)] = nBar;
                
                    F_PRINT(hFile, "Close    %f", arrLow[nBar], 
                        "    %f\r\n", 
                        arrBalance[ARRAY_SIZE(arrBalance) - 1]);
                }

                // Sell short
                dOpenPrice = arrLow[nBar];
                dStop = dOpenPrice + dStopLoss;
                nType = 1;

                nTradeNumber = nTradeNumber + 1;

                F_PRINT(hFile, "%.0f    ", nTradeNumber, 
                    "Sell    %f    ", arrLow[nBar]);
            }
        }
    }
    // 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 - 1] 
            - dSpread) * dLotSize;
        arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;
        F_PRINT(hFile, "Close    %f", arrHigh[nBar], "    %f\r\n", 
            arrBalance[ARRAY_SIZE(arrBalance) - 1]);
    }
    else
    {
        if(nType == -1)
        {
            arrBalance[ARRAY_SIZE(arrBalance)] = 
            arrBalance[ARRAY_SIZE(arrBalance) - 1] + 
                100 * (arrLow[nBar - 1] - dOpenPrice) * dLotSize;
            arrBars[ARRAY_SIZE(arrBars)] = nBar - 1;

            F_PRINT(hFile, "Close    %f", arrLow[nBar], "    %f\r\n",  
                arrBalance[ARRAY_SIZE(arrBalance) - 1]);
        }
    }

    F_PRINT(hFile, "Balance: %f", 
        arrBalance[ARRAY_SIZE(arrBalance) - 1], 
        ", Max. Drawdown: %f", dMaxDrawDown,
        ", Number of trades: %.0f", nTradeNumber);
    
    F_CLOSE(hFile);
}

Few words about the drawdown. There are few ways of calculating it, and we are using what I consider the most "honest". The drawdown is a measure of instability of our system. What is a chance, that it will loose money? Lets say the initial amount is $1000. If the profit goes 100, 200, 300, 400..., the drawdown is 0. If it goes 100, 200, 100..., then the drawdown is 0.1 (10%), as we have just lost an amount, equal to 1/10 of the initial deposit (from 1200 to 1100).

I would strongly advice against using trading systems with large drawdowns.

Also, here I use a drawdown, that is to be used with variable lot size. However, in the actual samples, that come with the eBook, you will see another version:

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

As you can see, here we always use 1000 (the initial amount) to calculate the drawdown. The reason is simple: we always use the same lot size (no money management yet), so there is no difference, how much money we have already accumulated on our account, an average profit should be constant. The worse possible scenario in this case looks like this: from the very beginning ($1000 on account) we are loosing money. If we use 1000$ to calculate the drawdown, we will get the worse drawdown. This will help us not to trick ourselves. For example, say, we traded for some time, and we have $10,000$ on our account. Then we loose some money, and we now have $8,000. Then we have recovered, and got $12,000. Good trading system? Probably not.

Let's repeat the logic again, as it is very important (and it will become even more important, when we start doing money management). We trade using fixed size lots. So, statistically, there is no guarantee, that the maximum loss will not happen at the very beginning, when we only have $1000. And if it happens, we will have -1000$ (10,000 - 8,000), so the trading system is probably too risky.

When we talk about the money management (probably, not in this text), we will have to use different approach to drawdown calculation.


Note, that in this trading system, I am using the worse possible scenario: I am buying using High and selling, using Low. Many testers do not follow these rules, and create trading systems, that work fine on historical data. But in the real life, these trading systems have very poor performance. Why?

Take a look at the price bar. It has Open, High, Low and Close. Do you know, how the price was moving inside the bar? No. So, let's say, your trading system generated a "buy" signal, at the bottom of the price bar (if dLow < dBuyLevel then buy). What should be the price of the execution for this order? Can it be "Low"? No. Because you have no guarantee, that the price within the bar moved from Open to Low, produced your trading signal and then jumped up to the High and stayed there for the rest of the bar duration.

One way to GUARANTEE, that in real trading your system will perform as good as during tests (or better), is to test it with the WORSE possible prices. Buy at High, close at Low. Sell at Low, close at High.

The other way is to trade only once per bar, when it just formed, and to use the PREVIOUS bars to calculate indicators. If you use this approach, you can open and close positions using the last (current) bar's OPEN. However, you cannot use its High, Low or Close, because you don't have them yet - the bar had just opened.


Note that I am using dLotSize equal 0.1 lot ($100). Obviously, in the "real" trading, you will benefit greatly, if the lot size is calculated depending on the money you have, something like:

forex_nn_01.tsc, part 3

dLotSize = FLOOR(arrBalance[
    ARRAY_SIZE(arrBalance) - 1] / 1000) * 100;
if(dLotSize < 100)
{
    dLotSize = 100;
}

However, we are doing testing here, not trading. And for testing, we need, among other things, to see how smooth the profit curve is. This is much easier to do if the lot size is the same (in ideal situation, for dLotSize = 100 we will get a straight line, with some positive slope, while in case of the adjustable lot size we will get an exponent, that is much harder to analyze).

Later in this text, we will apply money management rules to our trading system, but not yet.

After we are done with the last part of our testing function, let's walk through the rest of the code.

The following function creates a CLV indicator. It takes the interval as a parameter, which means that we can call it many times, during the optimization, passing different numbers.

Note, that I am using the NN that works in the 0 - 1 interval. The data can be normalized, of course, but I chose to divide the indicator by 2 and to add 0.5, so that it is in 0 - 1 range.

forex_nn_01.tsc, part 4

array CreateClv(double nInterval)
{    
    PRINT("%s\r\n", "Creating CLV indicator");

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

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MVMIN(arrLow, nInterval);
    array arrPeriodHigh = MVMAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrClv[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];

        // / 2 + 1 to confine to 0...1 instead of -1...1
        arrClv[i] = (((dClose - dLow) - (dHigh - dClose))
            / (dHigh - dLow)) / 2 + 0.5; 
    }

    return arrClv;
}

To make lag file, we can use the CREATE_LAG_FILE function. Alternatively, we can do it by explicitly providing all the necessary code. In this case, we have more control, and we are going to need it, if we begin varying number of lagged columns and so on.

forex_nn_01.tsc, part 5

void CreateLagFile(string strLagFileName, array arrLags, 
    double nRemoveFirst)
{
    PRINT("%s\r\n", "Creating lag file");

    hFile = F_OPEN(strLagFileName, "wb");

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

    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", arrClv[i - arrLags[j]]);     
        }
        F_PRINT(hFile, "%s", "\r\n");
    }

    F_CLOSE(hFile);
}

The nRemoveFirst parameter is important. Many functions, like indicators, moving averages, lag generators, for that matter, do not work well within the first few records of the dataset. Let's say we have MA(14) - what will it place in the records 1 - 13? So we choose to simply remove the first few (unreliable) records.

For the NewNn, as well as for all functions of this program, we need to pass as parameters only what can be changed during optimization process. For example, there is no need to pass a "skip before" parameter, as it is always the same.

forex_nn_01.tsc, part 6

void NewNn(array arrLags, double dStopError, 
    double nStopEpoch, double nNeuronsLayer2, 
    string strNnFileName)
{
    PRINT("%s\r\n", "Creating NN");

    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 = 1; 
        nInputCol < ARRAY_SIZE(arrLags); 
        nInputCol = nInputCol + 1) 
    {
        arrInputColumns[nInputCol - 1] = nInputCol + 1;
        arrInputColumnNames[nInputCol - 1] = 
            "Clv-" + NUM2STR(arrLags[nInputCol], "%.0f"); 
    }

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

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

//  dStopError = 0;
//  nStopEpoch = 0; 
    
    // To do: Modify if more than one indicator is used
    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);    
    arrOutTabOutputColumns[1] = ARRAY_SIZE(arrLags) + 1;

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

    CREATE_NN(strNnFileName, 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);
}

The TeachNn function simply brings up the NN dialog.

forex_nn_01.tsc, part 7

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, bIsPathRelative, 
        bStartLearning, bResumeScript, bReset);
}

Finally, we need a charting function. It is not mandatory, but it is always a good idea to see what our profit line looks like. The following code uses the XML to produce a chart, so it is a good idea to read the tutorial. Alternatively, you can draw the chart, rather than saving it in a file. To do it, use one of the samples, that are in the samples/scripts directory. Finally, you can modify the code, to produce HTML, rather than XML. HTML is easier to learn, but the code itself will be a bit less readable.

forex_nn_01.tsc, part 8

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

    string strPath = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\images\\";
    
    string strProfitPath = strPath + strForexName + ".png";    
    
    strXML = strXML + "\t<symbol>\r\n";
    strXML = strXML + "\t\t<symbol>\r\n";
    strXML = strXML + "\t\t\t" + strForexName + 
        NUM2STR(arrBalance[ARRAY_SIZE(arrBalance) - 1], ", 
            Profit: %f") +  
        NUM2STR(nTradeNumber, ", Number of trades: %.0f") +
        NUM2STR(dMaxDrawDown, ", Max. Drawdown: %f\r\n");
    strXML = strXML + "\t\t</symbol>\r\n";

    strXML = strXML + SAVE_CHART(400, 500, 
        0, strProfitPath, arrBars, arrBalance);

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

    SAVE_XML(strPath, "chart_forex_nn", "chart_forex_nn", 
        "root", strXML);
    SHOW_XML(strPath + "chart_forex_nn.xml");
}

Compile and Run the script.

Well... As expected, using 7 hours as an interval for the CLV produced very poor results:




FOREX Trading Strategies and Optimization

The reason for the poor results is quite obvious: we used the Interval, Stop Loss, buy and sell levels and other parameters, that were purely random - we just picked first that came in mind! What if we try few combinations?

FOREX Trading Signals: What to optimize?

First of all, by overoptimizing the buy and sell levels, we can ruin our future performance. However we still can tune them, especially, if the performance is close for close values of buy and sell limits. For example, if we have -10% profit at buy limit equal 0.3, and +1000% profit when it equals 0.35, then there is probably a lucky coincidence, and we should not use 0.35 for our trading system, as in future it will probably not happen again. If, instead, we have -10% and +10% (instead of +1000%), it may be safer to use.

Generally, our trading system should be built for WORSE possible scenario, as if during the "real" trading the performance will be better, then during the test, we will survive, but not the other way around.

We can vary the value for the indicator interval, provided we have enough trades, so that we can be confident, in terms of statistics, in the performance of a system.

We certainly can vary the number of neurons, I don't think it can be overoptimized easily.

We can vary number of inputs and lags for inputs. It is possible to overoptimize this, but it is not very likely to happen.

And, of course, we can try different indicators.


Accurate FOREX Signals: How to optimize?

As have already been mentioned, if we start trying all possible combinations, it will take forever. So we are going to cheat. We will create pre-defined sets of parameters, that we think are reasonable, and pass them to the program.

To make as few calculations as possible, note, that Clv-1 and Clv-2 are, probably, important, but what about Clv-128? And - if we already have Clv-128, do we need Clv-129? Probably, not. So we are going to have something like Clv-1, Clv-2, Clv-4, Clv-8, ... Clv-128 with just few variations, which will make our calculation time thousands times shorter.




FOREX Professional System Trading: Can it work at all?

What is it exactly we want to predict? Until this point we have used 1 hour chart for EURUSD, and we were predicting the next bar's CLV. Will the CLV+2 be better? What about CLV+3?

Also, especially considering the poor performance of our first trading system, it would be nice to know, that - at least in the "ideal" world, the goal (profitable trading) can be achieved.

To answer these questions, let's create a simple testing program. We assume, that our prediction is 100 % accurate, and, based on this assumption, we will use CLV+N, not the NN predicted one. That's right - we are going to take data from the future, and to use them instead of the NN prediction. This approach wouldn't work in the real life, of course, but at leats, it will give us some ideas of what to expect.

When looking at the results, please keep in mind, that we are not using any advanced money management, our lot size is set to a minimum $100. If you use variable lot sizes, results will be dramatically different. But even at a lot size set to 0.1 we can see (below) that getting the information from the future is an ultimate trader's "holly graal".

forex_nn_02.tsc, part 1

void main()
{
    OUT_CLEANUP();

    string strImagePath = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\images\\";

    string strForexName = "EURUSD_H1";

    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]);
    }

    // ***** Loading data
    string strDataFileName = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\forex\\"
        + strForexName + ".TXT";
    double bIsPathRelative = 0;

    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);

You are already familiar with this code, it was used in FOREX_NN_01.TSC. It handles data loading. The only difference is in the part that obtains the list of files in the "images" directory and deletes all files with the .PNG extention. The reason for this code is simple: during our tests we are going to create many - may be, thousands - image files. We don't want them to hung around after we are done. So at the beginning of the script we are deleting images, created by other scripts.

forex_nn_02.tsc, part 2

    array arrClvIntervals = CREATE_ARRAY(0);
    arrClvIntervals[0] = 4;   
    arrClvIntervals[1] = 6;   
    arrClvIntervals[2] = 8;   
    arrClvIntervals[3] = 12;  
    arrClvIntervals[4] = 16;   
    arrClvIntervals[5] = 24;
    arrClvIntervals[6] = 32;   
    arrClvIntervals[7] = 48;

    array arrMa = CREATE_ARRAY(0);
    arrMa[0] = 1;    // No MA
    arrMa[1] = 4;
    arrMa[2] = 7;
    arrMa[3] = 14;

    // How far in future we want to predict
    array arrOutLag = CREATE_ARRAY(0);
    arrOutLag[0] = 0;    // Current
    arrOutLag[1] = 1;    // 1 bar
    arrOutLag[2] = 2;
    arrOutLag[3] = 3;
    arrOutLag[4] = 4;
    // Note: from this point coinsides with arrClvIntervals
    arrOutLag[5] = 6;    
    arrOutLag[6] = 8;    
    arrOutLag[7] = 12;  
    arrOutLag[8] = 16;   
    arrOutLag[9] = 24;
    arrOutLag[10] = 32;   
    arrOutLag[11] = 48;

    array arrStopLoss = CREATE_ARRAY(0);
    arrStopLoss[0] = 0.0025;
    arrStopLoss[1] = 0.005;
    arrStopLoss[2] = 0.0075;
    arrStopLoss[3] = 0.01;

    array arrClv = CREATE_ARRAY(0);
    array arrClvSmooth = CREATE_ARRAY(0);

    double nNetNum = 0;
    double nRemoveFirst = 128;

    string strXML = "<forex>\r\n";

    double nInterval;
    double nMaIdx;
    double nMa;
    double nOutLagIdx;
    double nOutLag;
    double dStopLoss;

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

    double dExpectedCycles = ARRAY_SIZE(arrClvIntervals) 
        * ARRAY_SIZE(arrMa) * ARRAY_SIZE(arrOutLag) * 
        ARRAY_SIZE(arrStopLoss) * 6 * 10 * 10;

Just a few comments. We do not want to try all possible values for, for example, CLV interval. Instead, we can create an array, that contains only values we want to test. Then (see below) we will walk through this array.

Stop losses are important part of any trading strategy, so I have decided to vary them as well. It is a dangerous idea, however, as it is easy to overoptimize the system.

I am planning to test different values for buy and sell levels, but it will be done in cycle, without using arrays.

Unlike in our previous example, we want to have a large XML file, containing many images. To do it, I have moved the code, that is forming the XML header and footer outside of the Chart function. Read one of the online XML tutorials for details.

Note, that I am using 0 as the first lag, which means, that first I am testing the indicator (CLV) that was not "shifted" from the future. Just to get an idea, how good out "trading system" would be without NN (horrible, is the right word. It is loosing all the money).

Cortex uses the Internet Explorer control to display XML pages. When pages grow large, it takes a lot of memory. If your computer cannot handle it, consider creating multiple XML or HTML pages, instead. In the case of forex_nn_02, it should not be a problem, as the page is relatively short. Alternatively (that is what I am doing in scripts later in this text), create XML file, but do not open it from Cortex. Open them using Internet Explorer instead - unlike IE control, the Internet Explorer does not have the memory problem.

Now the code that is trying different combinations of parameters.

forex_nn_02.tsc, part 3

    double nWinners = 0;

    for(double nIntervalIdx = 0; 
        nIntervalIdx < ARRAY_SIZE(arrClvIntervals);
        nIntervalIdx = nIntervalIdx + 1)
    {
        nInterval = arrClvIntervals[nIntervalIdx];

        ARRAY_REMOVE(arrClv, -1);
        arrClv = CreateClv(nInterval);

        for(nMaIdx = 0; nMaIdx < ARRAY_SIZE(arrMa); 
            nMaIdx = nMaIdx + 1)
        {
            OUT_CLEANUP();
            nMa = arrMa[nMaIdx];

            ARRAY_REMOVE(arrClvSmooth, -1);
            arrClvSmooth = EXP_MVAVG(arrClv, nMa);

            for(nOutLagIdx = 0; nOutLagIdx < ARRAY_SIZE(arrOutLag); 
                nOutLagIdx = nOutLagIdx + 1)
            {
                nOutLag = arrOutLag[nOutLagIdx];

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

                    for(double dTakeProfit = 0; dTakeProfit <= 0.05; 
                        dTakeProfit = dTakeProfit + 0.01)
                    {
                        for(double nBuyIdx = 0; nBuyIdx < 10; 
                            nBuyIdx = nBuyIdx + 1)
                        {
                            double dBuyLevel = 0.15 + 0.02 * nBuyIdx;

                            for(double nSellIdx = 0; nSellIdx < 10; 
                                nSellIdx = nSellIdx + 1)
                            {
                                double dSellLevel = 
                                    0.85 - 0.02 * nSellIdx;

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

                                Test();

                                PRINT(" (%.0f)\r\n", nWinners);

                                if(ARRAY_SIZE(arrBalance) <= 1 || 
                                    (ARRAY_SIZE(arrBalance) > 1 && 
                                    (arrBalance[ARRAY_SIZE(arrBalance)
                                        - 1] <= 5000 ||
                                        arrBalanceBuy[ARRAY_SIZE(
                                            arrBalanceSell) - 1] 
                                            <= 3000 ||
                                        arrBalanceSell[ARRAY_SIZE(
                                            arrBalanceSell) - 1] 
                                            <= 2000)) ||
                                        nTradeNumber < 50)
                                {
                                    continue nBuySellIdx;
                                }

                                nWinners = nWinners + 1;
                                            
                                Chart(strForexName);
                            }
                        }
                    }
                }
            }
        }
    }

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

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

Here, we are using nested cycles. In every cycle, we are assidning some variable (for example, nInterval for the outer cycle). This way the cycle will assign values of all elements of a corresponding array, one in a time. Then WITHIN it, the inner cycle is used, and so on, so that all combinations of all array elements are tested.

In the innermost cycle, I am calling the Test() function, to "test trade", and Chart() to add a new picture to a list of images saved on disk. Note, that this Chart() does not show any images, until all cycles are completed.

The Test() and CreateClv() functions are almost the same as in the previous example. The only real difference is due to the fact that it is called more then once. To do it, I am calling ARRAY_REMOVE to cleanup arrays.

Also, notice, that we are only creating charts for the combinations of parameters, that produce trading system with positive profit. Otherwise, we call "continue", to skip the Chart() function.

Finally, we have Take Profit now, so our trading system can be a bit more flexible.

forex_nn_02.tsc, part 4

void Test()
{
    nNetNum = nNetNum + 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;

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

    double dSpread = 0.0005;
    double bStop;
    
    // Max. Drawdown
    double dMaxDrawDown = 0;
    
    // Used to calculate drawdown
    double dCurrentMax = arrBalance[0];

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

    for(double nBar = nRemoveFirst + 1; 
        nBar < ARRAY_SIZE(arrClose) - nRemoveFirst; 
        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] - 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]; 

                bStop = 1;
                dClosedAt = arrLow[nBar];
            }
            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] - dSpread) 
                        * dLotSize;

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

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

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

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

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

        if(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] < -500 ||
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] < -500 ||
            dMaxDrawDown > 0.2)
        {
            break nBar;
        }

        if(nType != -1 && 
            arrClvSmooth[nBar - 1 + nOutLag] <= dBuyLevel && 
            arrClvSmooth[nBar + nOutLag] >= dBuyLevel)
        {
            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;
            }

            dOpenPrice = arrHigh[nBar];
            dStop = dOpenPrice - dStopLoss;
            dTp = dOpenPrice + dTakeProfit;
            nType = -1;
            
            nTradeNumber = nTradeNumber + 1;
            nTradeNumberBuy = nTradeNumberBuy + 1;
        }
        else
        {
            if(nType != 1 && 
                arrClvSmooth[nBar - 1 + nOutLag] >= dSellLevel && 
                arrClvSmooth[nBar + nOutLag] <= dSellLevel)
            {
                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;
                }

                dOpenPrice = arrLow[nBar];
                dStop = dOpenPrice + dStopLoss;
                dTp = dOpenPrice - dTakeProfit;
                nType = 1;
                
                nTradeNumber = nTradeNumber + 1;
                nTradeNumberSell = nTradeNumberSell + 1;
            }
        }
    }

    // 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 - 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 - 1;
    }
    else
    {
        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 - 1;
        }
    }
}
// -------------------
array CreateClv(double nInterval)
{    
    PRINT("%s\r\n", "Creating CLV indicator");

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

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MVMIN(arrLow, nInterval);
    array arrPeriodHigh = MVMAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrClv[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];

        // / 2 + 1 to confine to 0...1 instead of -1...1
        arrClv[i] = (((dClose - dLow) - (dHigh - dClose))
            / (dHigh - dLow)) / 2 + 0.5; 
    }

    return arrClv;
}

The Chart() function was broken into two pieces. The header and the footer should be written to the XML file only once, so they were moved to the main part of the program.

Also, I am using the counter, to save files under the different names. The information about parameters is written to the header of an image, so we can easily see which one it is. Finally, images are only saved for winning configurations, meaning the balance at the end should be more, then at the beginning.

forex_nn_02.tsc, part 5

void Chart(strForexName)
{
    string strProfitPath = strImagePath + 
        strForexName + NUM2STR(nNetNum, "%.0f") + ".png";    
    
    strXML = strXML + "\t<symbol>\r\n";
    strXML = strXML + "\t\t<symbol>\r\n";
    strXML = strXML + "\t\t\t" + strForexName + 
        NUM2STR(arrBalance[ARRAY_SIZE(arrBalance) - 1], 
            ", Profit: %f") +  
        NUM2STR(nTradeNumber, ", Number of trades: %.0f") +
        NUM2STR(dMaxDrawDown, ", Max. Drawdown: %f") +
        NUM2STR(nInterval, ", CLV: %.0f") +
        NUM2STR(nMa, ", MA: %.0f") +
        NUM2STR(nOutLag, ", Out.Lag: %.0f") +
        NUM2STR(dStopLoss, ", Stop: %f") +
        NUM2STR(dTakeProfit, ", TakeProfit: %f") +
        NUM2STR(dBuyLevel, ", Buy: %f") + 
        NUM2STR(dSellLevel, ", Sell: %f\r\n");

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

    strXML = strXML + "\t\t" + 
        SAVE_CHART(400, 500, 0, strProfitPath, arrBars, arrBalance);

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

Run the program (it will take some time to complete). You will end up with a large XML page with images, one for each winning configuration.

Some of the results are great, however, as we used data "from the future", this system will not work in the real life. Actually, if you look at the Test() function, you will notice, that the cycle stops before we reach the last element of arrClose:

for(nBar = nRemoveFirst + 1; nBar <
    ARRAY_SIZE(arrClose) - nRemoveFirst; ...

The reason is, we don't have the "future" data for the last elements. However, we can use the NN to create a prediction.


Neural Networks FOREX Trading

Now that we know, what could have been done, IF we had glimps of information from the future, we can use this information to build a trading system, that does not "cheat", using NN prediction instead of the future quotes (that are not available in the real life).

Once again, note, that the purpose of this text is to show you, how to use Cortex, not how to create a perfect trading system.

In the code below, we are going to try different combinations of Interval, MA, stop loss / take profit, buy/sell levels and so on. Some of these parameters (CLV interval, MA and output lag) are taken from the "winners", that we have found in the forex_nn_03 script.

The code below has some comments, also, by this time you should be able to understand it without help.

forex_nn_03.tsc

void main()
{
    OUT_CLEANUP();

    string strImagePath = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\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 strNnPath = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\nn\\";
    string strNnFileName = strNnPath + strForexName + ".nn";

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

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

    string strDataFileName = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\forex\\"
        + strForexName + ".TXT";
    string strLagFileName = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\forex\\"
        + strForexName + ".lgg";
    
    double bIsPathRelative = 0;

    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);

    // Interval, Ma, OutLag 
    array_s arrParameters = CREATE_ARRAY_S(0);
    arrParameters[0] = "24,4,2";
    arrParameters[1] = "24,4,3";
    arrParameters[2] = "24,7,2";
    arrParameters[3] = "24,7,3";
    arrParameters[4] = "24,7,4";
    arrParameters[5] = "32,4,2";
    arrParameters[6] = "32,4,3";
    arrParameters[7] = "32,7,4";
    arrParameters[8] = "48,7,8";

    array arrClv;
    array arrClvSmooth;

    double nNetNum = 1;
    double nRemoveFirst = 200;

    double dStopError = 0;
    double nStopEpoch; 

    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] = 
        "18,0,1,2,3,4,6,8,12,14,16,20,24,28,32,40,48,56,64";
    arrStrLags[1] = 
        "20,0,1,2,3,4,6,8,12,14,16,20,24,28,32,50,70,90,110,130,150";

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

    array arrWinLag = CREATE_ARRAY(0);
    array arrWinStopLoss = CREATE_ARRAY(0);
    array arrWinTakeProfit = CREATE_ARRAY(0);

    array arrWinBuyLevel = CREATE_ARRAY(0);
    array arrWinSellLevel = CREATE_ARRAY(0);

    array arrWinNeurons = CREATE_ARRAY(0);
    array_s arrWinNnLags = CREATE_ARRAY_S(0);
    array arrWinProfit = CREATE_ARRAY(0);
    array arrWinProfitBuy = CREATE_ARRAY(0);
    array arrWinProfitSell = CREATE_ARRAY(0);

    array arrNn = CREATE_ARRAY(0);

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

    string strParam;
    string strToken;
    double nMa;
    double nOutLag;
    double hNn;
    double dBuyLevel;
    double dSellLevel;
    double nInterval;
    double nExtractRecords;

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

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

        arrClv = CreateClv(nInterval);

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

        arrClvSmooth = EXP_MVAVG(arrClv, nMa);

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

        for(double nLagIdx = 0; nLagIdx < ARRAY_SIZE(arrStrLags); 
            nLagIdx = nLagIdx + 1)
        {
            double nNumOfLags;
            string strLagBuf = arrStrLags[nLagIdx];
            CreateLagFile(strLagBuf, nRemoveFirst);

            double nNeurons = (nNumOfLags + 1) / 2 + 1;
            nStopEpoch = nNeurons * 1000;

            nCounter = nCounter + 1;
            
            NewNn(arrLags, dStopError, nStopEpoch, nNeurons);
            TeachNn();

            hNn = OPEN_NN(strNnFileName, bIsPathRelative);
            APPLY_NN(hNn, nExtractRecords, 1.0, 1, 
                arrClvSmooth, arrLags, 1, arrNn);
            CLOSE_NN(hNn);

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

                for(double dTakeProfit = 0; dTakeProfit <= 0.05; 
                dTakeProfit = dTakeProfit + 0.01)
                {
                    for(double nBuySellIdx = 0; nBuySellIdx < 24; 
                        nBuySellIdx = nBuySellIdx + 1)
                    {
                        dBuyLevel = 0.1 + 0.01 * nBuySellIdx;
                        dSellLevel = 0.9 - 0.01 * nBuySellIdx;

                        PRINT("%.0f", nCounter, " of %.0f", 
                            dExpectedCycles, " (%.0f): ", 
                            nSelectedCounter, "%.0f\r\n", nNetNum);

                        Test();

                        if(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] 
                            > 1000 
                            && arrBalanceSell[ARRAY_SIZE(arrBalanceSell) 
                            - 1] > 1000)
                        {
                            nSelectedCounter = nSelectedCounter + 1;

                            SaveWin();

                            double dBuyLevelTmp = dBuyLevel;
                            double dSellLevelTmp = dSellLevel;
                                    
                            dBuyLevel = dBuyLevel - 0.003;
                            dSellLevel = dSellLevel + 0.003;

                            Test();
                            if(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) 
                                - 1] > 1000 
                                && arrBalanceSell[ARRAY_SIZE(
                                    arrBalanceSell) - 1] > 1000)
                            {
                                SaveWin();
                            }

                            dBuyLevel = dBuyLevel + 0.006;
                            dSellLevel = dSellLevel - 0.006;

                            Test();
                            if(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) 
                                - 1] > 1000 
                                && arrBalanceSell[ARRAY_SIZE(
                                    arrBalanceSell) - 1] > 1000)
                            {
                                SaveWin();
                            }

                            dBuyLevel = dBuyLevelTmp;
                            dSellLevel = dSellLevelTmp;
                        }
                    }
                }
            }
        }
    }

    Chart(strForexName);

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

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

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

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

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

    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 = arrBalance[0];        // To calculate drawdown

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

    for(double nBar = nRemoveFirst + 1; 
        nBar < ARRAY_SIZE(arrClose) - nRemoveFirst; 
        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] - 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]; 

                bStop = 1;
                dClosedAt = arrLow[nBar];
            }
            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] - dSpread) 
                            * dLotSize;

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

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

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

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

        if(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] < 500 ||
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] < 500)
        {
            break nBar;
        }

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

        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] - 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;
            }

            dOpenPrice = arrHigh[nBar];
            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] - 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;
                }

                dOpenPrice = arrLow[nBar];
                dStop = dOpenPrice + dStopLoss;
                dTp = dOpenPrice - dTakeProfit;
                nType = 1;
                
                nTradeNumber = nTradeNumber + 1;
                nTradeNumberSell = nTradeNumberSell + 1;
            }
        }
    }

    // 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 - 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 - 1;
    }
    else
    {
        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 - 1;

        }
    }

    if(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] > 1000 &&
        arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] > 1000)
    {
        PRINT("%.0f", nNetNum, ". Trades: %.0f", nTradeNumber, 
            " (B: %.0f", nTradeNumberBuy, 
            ", S: %.0f", nTradeNumberSell, 
            "), Balance: %f", arrBalance[ARRAY_SIZE(arrBalance) - 1], 
            " (%f", arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1], 
            ", %f)\r\n", arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1], 
            "\tI: %.0f", nInterval, ", Ma: %.0f", nMa, ", Out: %.0f", 
            nOutLag, 
            ", Stop: %.4f", dStopLoss, ", Tp: %.4f\r\n", dTakeProfit, 
            "\tBuy: %.3f", dBuyLevel, ", Sell: %.3f", dSellLevel, 
            ", Neurons: %.0f", nNeurons, ", Lags: %s\r\n", strLagBuf);
    }
}

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

array CreateClv(double nInterval)
{    
    PRINT("%s\r\n", "Creating CLV indicator");

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

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MVMIN(arrLow, nInterval);
    array arrPeriodHigh = MVMAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrClv[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];

        // / 2 + 1 to confine to 0...1 instead of -1...1
        arrClv[i] = (((dClose - dLow) - (dHigh - dClose))
            / (dHigh - dLow)) / 2 + 0.5; 
    }

    return arrClv;
}

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

void SaveWin()
{
    if(arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1] > 1000 
        && arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1] > 1000)
    {
        double nWinIdx = ARRAY_SIZE(arrWinInterval);

        arrWinInterval[nWinIdx] = nInterval;
        arrWinMa[nWinIdx] = nMa;
        arrWinLag[nWinIdx] = nOutLag;         
        arrWinStopLoss[nWinIdx] = dStopLoss;
        arrWinTakeProfit[nWinIdx] = dTakeProfit;
        arrWinBuyLevel[nWinIdx] = dBuyLevel;
        arrWinSellLevel[nWinIdx] = dSellLevel;
        arrWinNeurons[nWinIdx] = nNeurons;
        arrWinNnLags[nWinIdx] = arrStrLags[nLagIdx];
        arrWinProfit[nWinIdx] = arrBalance[ARRAY_SIZE(arrBalance) - 1];
        arrWinProfitBuy[nWinIdx] = 
            arrBalanceBuy[ARRAY_SIZE(arrBalanceBuy) - 1];
        arrWinProfitSell[nWinIdx] = 
            arrBalanceSell[ARRAY_SIZE(arrBalanceSell) - 1];

        string strWinNnFileName = strNnPath + strForexName + "_" 
            + NUM2STR(nWinIdx, "%.0f") + ".nn";    
        F_COPY(strNnFileName, strWinNnFileName);
    }
}

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

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

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

    ARRAY_REMOVE(arrLags, -1);
    string strToken = GET_TOKEN(strLags, ",");
    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, ",ClvMa%.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", arrClvSmooth[i]);     

        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrClvSmooth[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;    // Clv

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

    nExtractRecords = 0.7 * ARRAY_SIZE(arrClose);

    // To do: Modify if more than one indicator is used
    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] = "Clv"; 
    arrOutTabOutputColumnNames[1] = "NN: Clv" ;

    CREATE_NN(strNnFileName, 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, bIsPathRelative, 
        bStartLearning, bResumeScript, bReset);
}

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

void Chart(strForexName)
{
    PRINT("%s\r\n", "Processing winning configurations...");

    string strXML = "<forex>\r\n";
    string strWinNnFileName;
        
    for(double nWin = 0; nWin < ARRAY_SIZE(arrWinInterval); 
        nWin = nWin + 1) 
    {
        PRINT("%.0f...", nWin);
        
        if(arrWinProfit[nWin] > 1000)
        {
            PRINT(" %s\r\n", "accepted");
            nInterval = arrWinInterval[nWin];
            nMa = arrWinMa[nWin];
            nOutLag = arrWinLag[nWin];
            dStopLoss = arrWinStopLoss[nWin];
            dTakeProfit = arrWinTakeProfit[nWin];
            dBuyLevel = arrWinBuyLevel[nWin];
            dSellLevel = arrWinSellLevel[nWin];
            strLagBuf = arrWinNnLags[nWin];

            arrClv = CreateClv(nInterval);
            arrClvSmooth = EXP_MVAVG(arrClv, nMa);

            CreateLagFile(strLagBuf, nRemoveFirst);

            strWinNnFileName = strNnPath + strForexName + 
                "_" + NUM2STR(nWin, "%.0f") + ".nn";

            hNn = OPEN_NN(strWinNnFileName, bIsPathRelative);
            APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrClvSmooth, 
                arrLags, 1, arrNn);
            CLOSE_NN(hNn);

            nNetNum = nWin - 1;
            Test();
            
            string strImageFileName = strImagePath + 
                strForexName + "_" + 
                NUM2STR(nWin, "%.0f") + ".png";    
            
            strXML = strXML + 
                "\t<symbol>\r\n\t\t<symbol>\r\n";

            strXML = strXML + "Trades:" + NUM2STR(nTradeNumber, "%.0f")
                + "(Buy:" + NUM2STR(nTradeNumberBuy, "%.0f")
                + ",Sell:" + NUM2STR(nTradeNumberSell, "%.0f")
                + "),I:" + NUM2STR(nInterval, "%.0f")
                + ",Ma:" + NUM2STR(nMa, "%.0f")
                + ",Lag:" + NUM2STR(nOutLag, "%.0f")
                + ",Stop:" + NUM2STR(dStopLoss, "%.4f")
                + ",Tp:" + NUM2STR(dTakeProfit, "%.4f")
                + ",Buy:" + NUM2STR(dBuyLevel, "%.3f")
                + ",Sell:" + NUM2STR(dSellLevel, "%.3f")
                + ",Neurons:" + NUM2STR(arrWinNeurons[nWin], "%.0f")
                + ",Nn Lags:" + strLags
                + ",Balance:" + NUM2STR(arrWinProfit[nWin], "%f")
                + ",Balance long:" + 
                    NUM2STR(arrWinProfitBuy[nWin], "%f")
                + ",Balance short:" + 
                    NUM2STR(arrWinProfitSell[nWin], "%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";
        }
        else
        {
            PRINT(" %s\r\n", "rejected");
        }
    }

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

The program will take a very long time to work. The result will be a long list of charts, from which we can choose the one we "trust" in terms of drawdowns, profits and other criteria. Here is an example of a chart:

As you can see, the system is still loosing money. In the following chapters we will focus on making it profitable.




NOC indicator as an input of our FOREX Trading System

Why our trading system fails to produce profit?

To answer this question, lets take a closer look at the input we use. The CLV indicator is one of the closest things to the price, except it is normalized to the 0 - 1 range. By normalizing the price, we prevent the Neural Net from doing its own normalization.

The difference is, that NN would normalize the data to the range from smallest value of a Learning Set, to the largest value, while we normalize to the max - min of a fixed length "sliding" window (nClvInterval).

An advantage of our approach is that the price does not go too far during nClvInterval of bars, and therefore, the chart has "more resolution".

There is, however, one disadvantage, too. Lets say, the price is moving up, from 90 to 100 (just an example). Then the range is 10. If we have buy level of our trading system at 0.1, it will correspond to a price move equal 1 (10 * 0.1), same with sell level equal 0.9 ((1 - 0.9) * 10).

Now, lets say our market is moving sideways, and the price moves between 90 and 91 (range is equal 1, 10 times smaller than in a previous example). Then for the same buy level equal 0.1 and sell level equal 0.9, we will get trading signals, when the price moves 0.1, not 1!

In other words, the smaller the range is, the more sensitive our trading system becomes. Which, obviously, can be a problem.

Note, that it is a good illustration to the following statement: Neural Networks can do a lot, but the actual thinking MUST be done by a human.

Often, people provide all indicators they have to the Neural Network, without thinking, and expect it to come up with a good prediction. Our little (well, not that little :) example shows why it usually does not work.

In this chapter a solution to this problem will be provided. Just keep in mind that it is a partial solution because a) as have already been mentioned, this text is not about creating a perfect trading system, but rather about showing the possible problems on that path, and some ways of avoiding them and b) it certainly can be improved, but these improvements are beyond the scope of the current book.

The NOC indicator

I have invented it for the purpose of this article. However, as it is very simple, I cannot be sure that someone haven't invented it before me. If so, I apologise, and will add a proper reference, when get one.

"NOC" means "Normalize On Condition". The idea is very simple: when the CLV range becomes too small, so that even smallest price moves result in the wide oscillation of the indicator, we should stop using this range. Instead, we use the fixed, "minimum" value, and perform the normalization with it.

Lets take a look at the code:

forex_nn_04.tsc, fragment

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 = MVMIN(arrLow, nInterval);
    array arrPeriodHigh = MVMAX(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;
}

As you can see, it is similar to the CLV code, with an additional condition, that works when the range becomes too narrow.

As the result, we still have the chart, that is more or less similar to the price, it still is normalized, and it does not have the "whipsaw" effect at the flat parts of the charts.

This indicator can, of course, be improved, but we are not going to do it in this text. Lets mention one thing: instead of using the treshold, at which the behaviour of the indicator changes sharply, we can use more sophisticated approach, to make indicator less sensitive at narrow parts of the chart, but smoothly. There are some other possible improvements.

Also, the overall logic can still be criticized. The 0-1 range will now not produce trading signals, which is good, however the 90-100 and 100-200 will be normalized to the same 0-1 range, therefore, the sensitivity of our indicator will be less when the price moves fast. It is not necessarily bad, if we use this fact in our trading system, but it is something to keep in mind, especially if the results are poor and we need to figure out why.

Here is a simple script, that creates charts for both CLV and NOC indicators.

forex_nn_04.tsc

void main()
{
    OUT_CLEANUP();

    string strImagePath = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\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 = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\forex\\"
        + strForexName + ".TXT";
    
    double bIsPathRelative = 0;

    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);

    array arrClv;
    array arrNormOnCondition;

    double nInterval = 64;
    double dMinRange = 0.03;

    arrClv = CreateClv(nInterval);
    arrNormOnCondition = CreateNoc(nInterval, dMinRange);

    Chart(strForexName);

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

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

array CreateClv(double nInterval)
{    
    PRINT("%s\r\n", "Creating CLV indicator");

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

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MVMIN(arrLow, nInterval);
    array arrPeriodHigh = MVMAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrClv[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];

        // / 2 + 1 to confine to 0...1 instead of -1...1
        arrClv[i] = (((dClose - dLow) - (dHigh - dClose))
            / (dHigh - dLow)) / 2 + 0.5; 
    }

    return arrClv;
}

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

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 = MVMIN(arrLow, nInterval);
    array arrPeriodHigh = MVMAX(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 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, 300, 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\tClv, ";
    strXML = strXML + "Interval = " + NUM2STR(nInterval, "%.0f\r\n");
    strXML = strXML + "\t\t</symbol>\r\n";
    strXML = strXML + "\t\t" + SAVE_CHART(400, 150, 1, 
        strImagePath + strForexName + "_clv.png", arrDate, arrClv);
    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, 150, 1, 
        strImagePath + 
        strForexName + "_noc.png", arrDate, arrNormOnCondition);
    strXML = strXML + "\t</symbol>\r\n";

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

CLV
NOC


Using Cortex Neural Networks with NOC indicator

Using NOC with Neural Networks is similar to using CLV. The difference between the script we already saw above and this one is in the fact, that here we only use BUY signals. The reason is simple: it seems, that to get good trading results, we need to use different parameters of the NOC indicator for buy and sell operations. So lets limit out study to the BUY only, as is is "for educational purposes only". The code, responsible for SALE (short) operations is still there, it is just commented.

Also note, that some code is commented, just to make the code to run faster. For example, I have found, that the following set of lags ("10,0,1,2,3,4,6,8,12,14,16") does not produce good results. So I have commented it. You can remove the comments, of couse, but it will slow the program down.

forex_nn_04b.tsc

void main()
{
    OUT_CLEANUP();

    string strImagePath = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\forex_nn_04b\\";

    string strForexName = "EURUSD_H1";

    string strNnPath = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\forex_nn_04b\\";
    string strNnFileName = strNnPath + strForexName + "_04b.nn";

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

    string strDataFileName = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\forex\\" 
        + strForexName + ".TXT";
    string strLagFileName = 
        "c:\\S_Projects\\CortexPro\\data\\stocks_nn\\forex_nn_04b\\"
            + strForexName + "_04b.lgg";
    
    double bIsPathRelative = 0;

    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);

    double nArraySize = ARRAY_SIZE(arrClose);

    // -------
    
    // Interval, Range, Ma, OutLag 
    array_s arrParameters = CREATE_ARRAY_S(0);
    arrParameters[0] = "12,0,3,2";
    arrParameters[1] = "12,0.004,5,2";
    arrParameters[2] = "12,0.008,5,2";
    arrParameters[3] = "12,0.012,5,2";
    arrParameters[4] = "24,0,3,2";
    arrParameters[5] = "24,0.004,3,2";
    arrParameters[6] = "24,0.008,3,2";
    arrParameters[7] = "24,0.012,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";
//  arrStrLags[1] = "10,0,1,2,3,4,6,8,12,14,16";
//  arrStrLags[2] = "12,0,1,2,3,4,6,8,12,14,16,18,20";
//  arrStrLags[3] = "9,0,1,2,3,4,8,12,16,20";
//  arrStrLags[4] = "13,0,1,2,3,4,6,8,10,12,16,20,24,32";

    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 nRemoveFirst = 200;

    double dStopError = 0;
    double nStopEpoch = 25000; 

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

    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);

                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)
                        {
                            double dBuyLevel = 0.1 + 0.01 * nBuyIdx;

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

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

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

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_nn", 
        "chart_forex_nn", "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; 
        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;
                }

/*              dOpenPrice = arrLow[nBar + 1];
                dStop = dOpenPrice + dStopLoss;
                dTp = dOpenPrice - dTakeProfit;
                nType = 1;
                
                nTradeNumber = nTradeNumber + 1;
                nTradeNumberSell = nTradeNumberSell + 1;
*/          }
        }

        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 = MVMIN(arrLow, nInterval);
    array arrPeriodHigh = MVMAX(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");

    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;    // Clv

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

    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] = "Clv"; 
    arrOutTabOutputColumnNames[1] = "NN: Clv" ;

    CREATE_NN(strNnFileName, 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, bIsPathRelative, 
        bStartLearning, bResumeScript, bReset);
}


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

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

    strXML = strXML + "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)

The first thing that you will notice, is the fact, that this time, by more carefully choosing the parameters of our trading system, we were able to find profitable combinations even for CLV (range equals 0). Does it mean that all our discussion about advantages of NOC over CLV is wrong?

Probably, not. What SHOULD be concluded from the charts above, is that BOTH methods are rather clumsy, and shouldn't be used AS IS for trading with the real money.

The problem is in the drawdown of our trading systems, which is inacceptably large. Statistically, it is possible for more than one "unlucky strikes" to happen in a row, which means that we shouldn't use systems with the DD larger that, say, 10 - 30%. Also, the sahpe of the profit curve for any of our combinations of parameters is far from linear.

As we were not trying to create a perfect trading system, it is OK. We have now the basics, and we can try to figure out what else should be improved, but, as I have mentioned already, this is not the purpose of this book.

However, one thing should be mentioned. Lets take another look at one of the charts:


Trades: 38 (Buy: 38, Sell :0)
NocInterval: 12, Range: 0.012, Ma: 5, Lag: 2,
Neurons: 5, Stop: 0.02, Stop increase: 0.3,
Buy: 0.21, Sell: 0.9, Drawdown: 0.97
Profit: 1774.00 (long: 1774.00, short: 0.00)

We can see, that winning and loosing trades are going in series. This certainly can be used to improve out results in one of two ways.

First: we can use the profit curve to adjust the size of the lot. way we will use larger lots during winning periods, and smaller lots during times, when our system does not perform well.

Of course, this approach will not work, if we have series of loosing trades, with single winning trade between them, so it is necessary to do a careful study of the profit curve.

This approach is an example of money management strategy, and it can improve some trading systems dramatically.

Second. The fact that our system is sometimes working and sometimes does not, means that there is a factor (or few factors) unaccounted for. We need to look for these factors, and to adjust the system, for example, by adding an extra indicator, or by modifying the exixting one.

For example, if our system is profitable only when the price goes up, all we need to do is to add a condition: open trades only when the short moving average is above the long one. This approach can help a lot sometimes, however, it may require changing the code a bit. For example, the NN may give a "buy" signal BEFORE the two MA cross, so we need to use a variable, holding the first signal, and then to wait for the second to happen.

It is also possible, as I already mentioned, that the indicator is not good enough for a job. What if the system works fine while the range is between 100 and 200 points, and does not work when it is larger (it means, the system does not work well, when the price moves too fast). Then we need to change the indicator, to compensate for a speed of a price change, and MAYBE our results will improve.

Once again, this is beyond the scope of this text.

Finally, this system is too risky, as it uses very large value for stop loss, so again, it is just an example.


Neural Network Trading: From testing to real FOREX Automated Trading System

Now that we have a Neural Network, we may want to trade using it. As Cortex is just a simulator, it cannot be used for trading in a real time.

Getting weights of the NN

Of course, it is possible to use the "run by timer" feature, and to perform file input and output, so that Cortex gets new quotes from the file, and writes results to the file. The Trading Platform you use will read the results, use them to trade, and then, as new quotes become available, write them to file.

This is not a very good approach. It is clumsy, and it can be error prone.

Another approach is to use Cortex API. However a) not all trading platforms will work with it, so you might need to write an additional level of software to wrap API functions, and b) not all traders like low level C++ programming.

The third approach uses DDE and ActiveX, but as Cortex does not provide this kind of functionality, it cannot be used.

Finally, you may write a SLANG function of your own (see the tutorial dealing with Cortex plugins). But again, it is an extra layer of complexty, so the current approach is still better.

The solution we suggest is to use ONLY scripting language of the trading platform of your choice.

First of all, we need to create the NN and to teach it. We did it in the chapters above, the result is the file with .NN extention.

Then note, that while the teaching requires slow and complex code, using the NN AFTER the teaching is complete is quite easy. The code (C++ code) for that is much shorter, and it does not take a lot of time to run, as there is no need to do thousands passes through the data patterns.

Therefore, IF we had the original C++ code AND weights of the fully trained network, we could write an identical script, using either SLANG or the scripting language of your favorite trading platform (like TS, MetaQuotes, MetaTrader...). Then you will end up with the script ONLY, that does not use DLLs, or any other kind of program-to-program communications, but still doing our NN calculations.

In the following chapters we are going to:
a) create a SLANG script with the same functionality as our NN
b) create the MetaTrader script to perform the same task

Among advantages of the MetaTrader (metaquotes.net), are small size (it is not a "monster"), price (it is free) and overall clearness of the interface.

Please note: though I consider the MetaTrader one of the most convenient trading platforms, I am not, in any way, related to it. This is not a promotion, and if you prefer other platforms, use them. That's exactly why I a have created the SLANG script first, and only then "cloned" it using MetaTrader's MQL.


Creating the NN script using SLANG

First of all, lets create the Cortex script, similar to forex_nn_04b.tsc, but without cycles. All we need here is to create a single NN, using "optimal" parameters, obtained in forex_nn_04b.tsc, to trade using it, and to produce the output chart.

The code below is the same as in forex_nn_04b.tsc, the only thing I did was commenting cycles and removing extras. It could be done in a more compact way, but I chose to keep the code as similar to the original forex_nn_04b.tsc as possible.

The code, responsible for creating and teaching the NN is commented in the script below. When you run it for the first time, you need to uncomment it, of course. After that, you don't need to recreate the same NN over and over again, so if you want to run the script for the second time, you may want to set comments back again.

forex_nn_05a.tsc

void main()
{
  OUT_CLEANUP();

  string strImagePath = 
    "h:\\S_Projects\\CortexPro\\data\\forex_nn\\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\\forex_nn\\nn\\som_05_winner_1.nn";

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

  string strDataFileName = 
    "..\\CortexPro\\data\\samples\\forex\\" 
      + strForexName + ".TXT";
  string strLagFileName = 
    "..\\CortexPro\\data\\forex_nn\\tmp\\"
      + strForexName + "_nn_05a.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);
    
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, bIsPathRelative);

    APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrNocSmooth, arrLags, 1, arrNn);
/*        
for(double nPat = 200; nPat < 1000; nPat = nPat + 1)
{
  PRINT("%s ", arrsDate[nPat], "%s ", arrsTime[nPat], "%f, ", 
      arrClose[nPat], "%f\r\n", arrNn[nPat]);
}
*/        
    for(double nStopIdx = 0; nStopIdx < ARRAY_SIZE(arrStopLoss); 
        nStopIdx = nStopIdx + 1)
    {
      double dStopLoss = arrStopLoss[nStopIdx];
i      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_nn", "chart_forex_nn", "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];
        
//PRINT("\t\t%s ", arrsDate[nBar], "%s ", arrsTime[nBar], "Close BUY by SL, %f, ", 
//    arrLow[nBar], " <= %f\r\n", dStop);
      }
      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;
      
//PRINT("%s ", arrsDate[nBar], "%s ", arrsTime[nBar], "Open BUY, Open: %f, ", 
//    dOpenPrice, "Stop: %f\r\n", dStop);
      
      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;
          
//PRINT("\t\t%s ", arrsDate[nBar], "%s ", arrsTime[nBar], "Close BUY, %f <= ", 
//  arrNn[nBar-1], " %f, ", dSellLevel, " >= %f\r\n", arrNn[nBar]);
        }
      }
    }

    if(dStopIncrease != 0)
    {
      if(nType == -1)
      {
        if(arrLow[nBar] - dStop >= dStopLoss * (1 + dStopIncrease))
        {
//PRINT("\t\t%s ", arrsDate[nBar], "%s ", arrsTime[nBar], 
//  "Move BUY stop from %f to ", dStop, "%f\r\n", arrLow[nBar] - dStopLoss);
          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;
      
//PRINT("\t\t%s ", arrsDate[nBar], "%s ", arrsTime[nBar], "Close BUY (final)%s", "\r\n");
    }
  }

  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";
}

Note the way program's output is formated. We can copy it from the output window and paste directly into our next SLANG program.

The program produces the chart, identical to the last chart in the forex_nn_04b.tsc section:


Exporting NN weights

The next step is to export weights of the NN to the scripting language of a trading platform of our choice. To do it, we add couple of PRINT statements to the tsc file above, and it will produce the necessary output. To do it, we simply insert the following code:

forex_nn_05b.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;
    }
  }
}

// ------        

Now that we have weights of the axons (of the neurons of our neural net), lets create the same network, using scripting language ONLY. It means, that we will not use functions of the scripting language, that are related with the NN functionality of Cortex, instead, we are going to emulate them.

This approach will allow us (in the next chapter) to move the code to almost ANY scripting language, even if it does not have NN functionality. For example, to the scripting language of your favorite trading platform.

First of all, lets take a look at the code that Cortex uses. This is a C++ code, and if you do not know C++, you can skip this part. This is just an illustration.

Keep in mind, that it is a "feedforward" part of the code. The complex one, that also takes a lot of resources to run, is the "backpropagation" one. But after the NN is created, we do not need it anymore.


THIS IS C++, just an example.

int NeuralNetwork::Forward(double* pdInput)
{
    for(int i = 0; i < m_nLayers; i++)
        m_ppLayers[i]->Forward(pdInput);
    
    return 1;
}

// ------

int NeuralLayer::Forward(double* pdInput)
{
    for(int i = 0; i < m_nNeurons; i++)
        m_ppNeurons[i]->Forward(pdInput, m_nLayerType);
    return 1;
}

// ------

double Neuron::Forward(double* pnInput, int nLayerType)
{
    double dLinearCombiner = (-1) * m_pdWeights[m_nInputs - 1];
    
    if(nLayerType != SP_INPUT_LAYER)
        for(int i = 0; i < m_nInputs - 1; i++)
            dLinearCombiner += m_pdWeights[i] * m_ppFrom[i]->m_dOutput;
    else
        for(int i = 0; i < m_nInputs - 1; i++)
            dLinearCombiner += pnInput[i] * m_pdWeights[i];

    m_dOutput = Activation(dLinearCombiner/nInputs);

    return m_dOutput;
}
///////////////////////////////////////////////////////
// Activation function
double Neuron::Activation(double u, int nType)    
{
    double dEx = tanh(u / 2);
    if(dEx == 1 || dEx == -1)
        dEx *= 0.999999999;
    return (nType == SP_SIGMOID) ? (dEx + 1) / 2 : dEx;
}

As you can see, the code is really simple. Now lets do the same using the SLANG script. As in examples before, we will keep the overall structure of the code, so that this example looks familiar. The only difference is that instead of using the built-in APPLY_NN function, we call the function of our own. The code that we do not use (such as cycles) is commented, but not removed.

Note, that the logic behind it was discussed in Neural Networks and Stock / Forex Trading article already. Briefly, the output of this script is formated to be compatible with the MQL, 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.

Porting script to trading platform

The next step is not really required, but it is something, that may be useful. We are going to create a version of a tsc file (one above), but this time, we will use SLANG (Cortex 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.

void main()
{
  OUT_CLEANUP();

  string strImagePath = 
    "h:\\S_Projects\\CortexPro\\data\\nn\\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\\nn\\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_nn", "chart_forex_nn", 
    "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 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[3] = -130.230957;
  arrWeights_0[4] = 122.264832;
  arrWeights_0[5] = -116.857647;
  arrWeights_0[6] = -87.211988;
  arrWeights_0[7] = -3.070481;
  arrWeights_0[8] = 16.478649;
  arrWeights_0[9] = 26.026662;
  arrWeights_0[10] = -159.377061;
  arrWeights_0[11] = -47.479122;
  arrWeights_0[12] = 104.071326;
  arrWeights_0[13] = -85.649556;
  arrWeights_0[14] = 188.245746;
  arrWeights_0[15] = 13.625241;
  arrWeights_0[16] = 140.068697;
  arrWeights_0[17] = 113.333588;
  arrWeights_0[18] = 1109.223634;
  arrWeights_0[19] = -914.960872;
  arrWeights_0[20] = 370.023213;
  arrWeights_0[21] = -28.047387;
  arrWeights_0[22] = 37.325713;
  arrWeights_0[23] = -226.078175;
  arrWeights_0[24] = 60.824913;
  arrWeights_0[25] = -121.224500;
  arrWeights_0[26] = 79.775097;
  arrWeights_0[27] = 4.628429;
  arrWeights_0[28] = -100.672645;
  arrWeights_0[29] = 97.028201;
  arrWeights_0[30] = 61.150900;
  arrWeights_0[31] = -159.271792;
  arrWeights_0[32] = 84.118150;
  arrWeights_0[33] = -9.871616;
  arrWeights_0[34] = -48.235015;
  arrWeights_0[35] = 77.718511;
  arrWeights_0[36] = 661.558706;
  arrWeights_0[37] = -411.591646;
  arrWeights_0[38] = 104.349090;
  arrWeights_0[39] = -99.837271;
  arrWeights_0[40] = 126.601131;
  arrWeights_0[41] = 107.472254;
  arrWeights_0[42] = 126.603749;
  arrWeights_0[43] = -34.495625;
  arrWeights_0[44] = 20.977627;
  arrWeights_0[45] = 3.895680;
  arrWeights_0[46] = -82.885884;
  arrWeights_0[47] = -33.776714;
  arrWeights_0[48] = -16.655781;
  arrWeights_0[49] = 215.236352;
  arrWeights_0[50] = 115.607084;
  arrWeights_0[51] = -12.600847;
  arrWeights_0[52] = -80.679499;
  arrWeights_0[53] = 242.773251;
  arrWeights_0[54] = 1031.385537;
  arrWeights_0[55] = -949.294602;
  arrWeights_0[56] = 187.067463;
  arrWeights_0[57] = -314.642807;
  arrWeights_0[58] = 148.656175;
  arrWeights_0[59] = 284.020192;
  arrWeights_0[60] = 82.173674;
  arrWeights_0[61] = -77.626693;
  arrWeights_0[62] = 7.912648;
  arrWeights_0[63] = 60.166694;
  arrWeights_0[64] = 18.410549;
  arrWeights_0[65] = 17.391980;
  arrWeights_0[66] = 162.603490;
  arrWeights_0[67] = -98.845268;
  arrWeights_0[68] = 21.648969;
  arrWeights_0[69] = 68.573664;
  arrWeights_0[70] = -129.288722;
  arrWeights_0[71] = 311.319087;
  arrWeights_0[72] = 1163.309077;
  arrWeights_0[73] = -964.935997;
  arrWeights_0[74] = 666.962079;
  arrWeights_0[75] = -283.212348;
  arrWeights_0[76] = -29.072518;
  arrWeights_0[77] = -71.041083;
  arrWeights_0[78] = -13.587105;
  arrWeights_0[79] = -64.148158;
  arrWeights_0[80] = -18.222318;
  arrWeights_0[81] = -130.446917;
  arrWeights_0[82] = -10.890569;
  arrWeights_0[83] = 113.613551;
  arrWeights_0[84] = 217.753494;
  arrWeights_0[85] = -311.183572;
  arrWeights_0[86] = -56.482977;
  arrWeights_0[87] = -100.310448;
  arrWeights_0[88] = 154.552381;
  arrWeights_0[89] = 137.994071;
  arrWeights_0[90] = 628.601433;
  arrWeights_0[91] = -415.789884;
  arrWeights_0[92] = 240.128736;
  arrWeights_0[93] = -189.593173;
  arrWeights_0[94] = 178.714259;
  arrWeights_0[95] = -72.434362;
  arrWeights_0[96] = -59.939457;
  arrWeights_0[97] = 1.622620;
  arrWeights_0[98] = -1.326195;
  arrWeights_0[99] = -12.406768;
  arrWeights_0[100] = -5.406124;
  arrWeights_0[101] = 11.512542;
  arrWeights_0[102] = 69.819580;
  arrWeights_0[103] = -49.454609;
  arrWeights_0[104] = 70.370092;
  arrWeights_0[105] = -5.417317;
  arrWeights_0[106] = 53.301957;
  arrWeights_0[107] = 212.577783;
  arrWeights_0[108] = 489.747638;
  arrWeights_0[109] = -47.747579;
  arrWeights_0[110] = 356.355751;
  arrWeights_0[111] = 12.281976;
  arrWeights_0[112] = 83.086865;
  arrWeights_0[113] = -22.621086;
  arrWeights_0[114] = 85.532027;
  arrWeights_0[115] = -8.316129;
  arrWeights_0[116] = -22.075525;
  arrWeights_0[117] = -5.974693;
  arrWeights_0[118] = 53.147061;
  arrWeights_0[119] = -9.791896;
  arrWeights_0[120] = 124.374967;
  arrWeights_0[121] = 94.065214;
  arrWeights_0[122] = 74.463043;
  arrWeights_0[123] = -53.090217;
  arrWeights_0[124] = -197.958978;
  arrWeights_0[125] = 610.478372;
  arrWeights_0[126] = 872.953472;
  arrWeights_0[127] = -454.008789;
  arrWeights_0[128] = 316.138742;
  arrWeights_0[129] = -53.098020;
  arrWeights_0[130] = -57.396066;
  arrWeights_0[131] = -231.539769;
  arrWeights_0[132] = 65.593983;
  arrWeights_0[133] = 98.067651;
  arrWeights_0[134] = 83.214865;
  arrWeights_0[135] = -135.525130;
  arrWeights_0[136] = -287.146326;
  arrWeights_0[137] = 2.720263;
  arrWeights_0[138] = 68.797985;
  arrWeights_0[139] = -139.371165;
  arrWeights_0[140] = -158.121714;
  arrWeights_0[141] = -146.721476;
  arrWeights_0[142] = 110.268264;
  arrWeights_0[143] = -183.986283;
  arrWeights_0[144] = 875.275098;
  arrWeights_0[145] = -178.004881;
  arrWeights_0[146] = 126.398123;
  arrWeights_0[147] = -194.081012;
  arrWeights_0[148] = 6.610336;
  arrWeights_0[149] = -87.990637;
  arrWeights_0[150] = 13.705421;
  arrWeights_0[151] = 44.991572;
  arrWeights_0[152] = 125.759172;
  arrWeights_0[153] = -122.537789;
  arrWeights_0[154] = -3.030823;
  arrWeights_0[155] = -36.755244;
  arrWeights_0[156] = -109.081299;
  arrWeights_0[157] = 13.706895;
  arrWeights_0[158] = -30.721436;
  arrWeights_0[159] = -71.412672;
  arrWeights_0[160] = 77.002588;
  arrWeights_0[161] = 423.219544;
  arrWeights_0[162] = 332.483426;
  arrWeights_0[163] = 2.666241;
  arrWeights_0[164] = 49.111954;
  arrWeights_0[165] = 25.015117;
  arrWeights_0[166] = 64.493639;
  arrWeights_0[167] = 7.458690;
  arrWeights_0[168] = -2.371083;
  arrWeights_0[169] = -15.076639;
  arrWeights_0[170] = -45.258685;
  arrWeights_0[171] = -16.860716;
  arrWeights_0[172] = -21.124351;
  arrWeights_0[173] = 24.885828;
  arrWeights_0[174] = -8.689909;
  arrWeights_0[175] = 2.180072;
  arrWeights_0[176] = 19.545566;
  arrWeights_0[177] = 23.089228;
  arrWeights_0[178] = 0.903966;
  arrWeights_0[179] = 51.994202;
  arrWeights_0[180] = 538.316425;
  arrWeights_0[181] = -263.600630;
  arrWeights_0[182] = 163.742209;
  arrWeights_0[183] = -120.650675;
  arrWeights_0[184] = 32.008412;
  arrWeights_0[185] = -21.026106;
  arrWeights_0[186] = -70.434916;
  arrWeights_0[187] = -16.096585;
  arrWeights_0[188] = 38.115713;
  arrWeights_0[189] = 27.837031;
  arrWeights_0[190] = 5.112014;
  arrWeights_0[191] = 32.051937;
  arrWeights_0[192] = -25.332025;
  arrWeights_0[193] = -70.777686;
  arrWeights_0[194] = 67.580428;
  arrWeights_0[195] = -9.746388;
  arrWeights_0[196] = -94.906632;
  arrWeights_0[197] = 166.774521;
  arrWeights_0[198] = 457.039764;
  arrWeights_0[199] = -245.320305;
  arrWeights_0[200] = 125.145628;
  arrWeights_0[201] = -90.931176;
  arrWeights_0[202] = 42.628177;
  arrWeights_0[203] = -48.379481;
  arrWeights_0[204] = -14.323804;
  arrWeights_0[205] = -32.374030;
  arrWeights_0[206] = 33.615677;
  arrWeights_0[207] = 46.242721;
  arrWeights_0[208] = 40.078101;
  arrWeights_0[209] = 56.229875;
  arrWeights_0[210] = -58.679682;
  arrWeights_0[211] = -75.272244;
  arrWeights_0[212] = 26.249448;
  arrWeights_0[213] = -36.083065;
  arrWeights_0[214] = -148.545840;
  arrWeights_0[215] = 69.829234;
  arrWeights_0[216] = 473.057597;
  arrWeights_0[217] = -182.640811;
  arrWeights_0[218] = 193.630779;
  arrWeights_0[219] = -39.101108;
  arrWeights_0[220] = 105.989570;
  arrWeights_0[221] = -34.845741;
  arrWeights_0[222] = 11.396567;
  arrWeights_0[223] = 7.375632;
  arrWeights_0[224] = 67.638279;
  arrWeights_0[225] = 18.946478;
  arrWeights_0[226] = -38.825600;
  arrWeights_0[227] = 19.639095;
  arrWeights_0[228] = -12.276608;
  arrWeights_0[229] = -41.479370;
  arrWeights_0[230] = 16.967973;
  arrWeights_0[231] = -26.950883;
  arrWeights_0[232] = -118.804194;
  arrWeights_0[233] = 74.012673;
  arrWeights_0[234] = 427.365549;
  arrWeights_0[235] = -7.130010;
  arrWeights_0[236] = -46.428358;
  arrWeights_0[237] = -100.177396;
  arrWeights_0[238] = -69.269862;
  arrWeights_0[239] = -42.469613;
  arrWeights_0[240] = -79.125184;
  arrWeights_0[241] = 32.714942;
  arrWeights_0[242] = 46.045283;
  arrWeights_0[243] = 71.389496;
  arrWeights_0[244] = 11.366095;
  arrWeights_0[245] = 35.890181;
  arrWeights_0[246] = 1.288889;
  arrWeights_0[247] = -36.168873;
  arrWeights_0[248] = 16.094410;
  arrWeights_0[249] = -8.426539;
  arrWeights_0[250] = -12.246986;
  arrWeights_0[251] = -11.679921;
  arrWeights_0[252] = 652.368249;
  arrWeights_0[253] = -281.203628;
  arrWeights_0[254] = 143.988766;
  arrWeights_0[255] = -206.389441;
  arrWeights_0[256] = 62.966834;
  arrWeights_0[257] = -41.260767;
  arrWeights_0[258] = 92.435259;
  arrWeights_0[259] = -34.447151;
  arrWeights_0[260] = -7.033507;
  arrWeights_0[261] = -67.193541;
  arrWeights_0[262] = 31.377436;
  arrWeights_0[263] = 95.665229;
  arrWeights_0[264] = 111.691227;
  arrWeights_0[265] = 8.980044;
  arrWeights_0[266] = -52.609443;
  arrWeights_0[267] = 44.082103;
  arrWeights_0[268] = 117.037182;
  arrWeights_0[269] = 361.990866;
  arrWeights_0[270] = 87.151436;
  arrWeights_0[271] = 67.855842;
  arrWeights_0[272] = 6.381954;
  arrWeights_0[273] = -7.413551;
  arrWeights_0[274] = -0.082699;
  arrWeights_0[275] = 7.611933;
  arrWeights_0[276] = 20.165410;
  arrWeights_0[277] = 25.353867;
  arrWeights_0[278] = 19.472293;
  arrWeights_0[279] = 47.533612;
  arrWeights_0[280] = 67.126236;
  arrWeights_0[281] = 57.354203;
  arrWeights_0[282] = 7.180279;
  arrWeights_0[283] = 0.252334;
  arrWeights_0[284] = -23.811531;
  arrWeights_0[285] = -8.133957;
  arrWeights_0[286] = 54.602033;
  arrWeights_0[287] = 32.664639;
  arrWeights_0[288] = 781.454739;
  arrWeights_0[289] = -607.609400;
  arrWeights_0[290] = 310.311980;
  arrWeights_0[291] = -72.538665;
  arrWeights_0[292] = 163.796243;
  arrWeights_0[293] = -96.205120;
  arrWeights_0[294] = 62.424626;
  arrWeights_0[295] = -30.809910;
  arrWeights_0[296] = -124.474945;
  arrWeights_0[297] = -241.936656;
  arrWeights_0[298] = -143.034299;
  arrWeights_0[299] = -27.625224;
  arrWeights_0[300] = 314.000569;
  arrWeights_0[301] = 142.585012;
  arrWeights_0[302] = 19.438387;
  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[3] = 7.247761;
  arrWeights_1[4] = 5.400128;
  arrWeights_1[5] = 4.255944;
  arrWeights_1[6] = 4.830370;
  arrWeights_1[7] = 6.498321;
  arrWeights_1[8] = 4.329279;
  arrWeights_1[9] = 21.221723;
  arrWeights_1[10] = 6.657713;
  arrWeights_1[11] = 4.870042;
  arrWeights_1[12] = 4.729068;
  arrWeights_1[13] = 17.062403;
  arrWeights_1[14] = 1.043106;
  arrWeights_1[15] = -14.022122;
  arrWeights_1[16] = 8.032901;
  arrWeights_1[17] = 32.278722;
  arrWeights_1[18] = 6.104636;
  arrWeights_1[19] = 5.363750;
  arrWeights_1[20] = 8.564406;
  arrWeights_1[21] = 7.089320;
  arrWeights_1[22] = 5.438834;
  arrWeights_1[23] = 4.473877;
  arrWeights_1[24] = 4.318535;
  arrWeights_1[25] = 6.570244;
  arrWeights_1[26] = 4.368093;
  arrWeights_1[27] = 20.619950;
  arrWeights_1[28] = 6.481450;
  arrWeights_1[29] = 4.540840;
  arrWeights_1[30] = 4.636043;
  arrWeights_1[31] = 16.533273;
  arrWeights_1[32] = 0.861578;
  arrWeights_1[33] = -14.068211;
  arrWeights_1[34] = 8.158290;
  arrWeights_1[35] = 31.833575;
  arrWeights_1[36] = 8.734738;
  arrWeights_1[37] = 10.190998;
  arrWeights_1[38] = 8.430947;
  arrWeights_1[39] = 8.285439;
  arrWeights_1[40] = 11.494198;
  arrWeights_1[41] = 9.383911;
  arrWeights_1[42] = 15.227542;
  arrWeights_1[43] = 9.827119;
  arrWeights_1[44] = 16.361780;
  arrWeights_1[45] = -17.675840;
  arrWeights_1[46] = 4.608113;
  arrWeights_1[47] = 6.859722;
  arrWeights_1[48] = 6.597408;
  arrWeights_1[49] = -9.915901;
  arrWeights_1[50] = 13.741963;
  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;

//if(nPattern == 100)
//{
//  PRINT("%.0f, ", nLag, "%f\r\n", arrPattern[nLag]);
//}
        }

        // 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";
}

After we run this function, we discover, that the result it produces is the same, as the forex_nn_05a produced, which means the code works fine. :

Note, that there is a difference at the beginning of the charts, as "our" NN does not try to process the data at the beginning (where lag is incomplete), while the built-in NN does not "know" about this problem. Of course, it doesn't affect the result, as the beginning of the chart is ignored by using the nRemoveFirst parameter in our script (set to 200, which is guaranteed to be larger, then our lag).

Using third-party trading platform

We have the NN that (more or less) can be used. We have the script, implementing this NN without calls to the Cortex-specific NN functions. Now we are going to port it to the trading platform that can be used for the real trading, which means it can contact brocker, place orders and earn (or loose) money.

As a trading platform, I am going to use MetaTrader

Disclaimer: I am not related to MetaQuotes in any way. I do not work for them, I am not their affiliate and so on. I use MetaTrader, ONLY because I like it.

I find this program user-friendly, flexible and powerful, and "not a monster". Also, it is free (compare to other packages of this class).

The only (minor) problem is that it is not always easy to find the dealer using MT in your area. Then, when you do a research, you may find couple of brockers, with screenshots on their web sites, that look suspiciously familiar. Yes, they use MetaTrader, but they don't call it MetaTrader!

I have asked for clarification at the company's forum, and they have told me, that they don't reveal brockers using their services. Very strange.

One of the brockers that is not hiding the fact they use MT, is Alpari. They will allow you to open a Demo account, so that you can trade in a real time, but without risking your money.

Warning!
I am not going to recommeng services of Alpari. Once again, I am not being paid for that. Try their Demo account, and use your own judgement. Or you can start your own research at Internet forums.

Finally, if you do not like the MT, you can probably follow the example below using TS, MS or some other trading platform. This is just an example.

Our MT-based trading system will include two files, the indicator and an expert. This is the way they call it in MQL (scripting language of MT), and I am going to follow this naming convention.

The indicator implements the neural network and draws a chart. An expert takes these data and does trading. As MetaTrader has a "strategy tester", we will be able to test our strategy, to see how good it is.

I will assume, that you are familiar with MQL programming, it is quite close to SLANG and tutorials can be found both at MetaQuotes and Alpari.

Finally, I am using the code structure, that is borrowed from MetaQuotes forum, permission to use it the author of the corresponding posts had granted me permission to use fragments of his code.

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[3] = -130.230957;
  arrWeights_0[4] = 122.264832;
  arrWeights_0[5] = -116.857647;
  arrWeights_0[6] = -87.211988;
  arrWeights_0[7] = -3.070481;
  arrWeights_0[8] = 16.478649;
  arrWeights_0[9] = 26.026662;
  arrWeights_0[10] = -159.377061;
  arrWeights_0[11] = -47.479122;
  arrWeights_0[12] = 104.071326;
  arrWeights_0[13] = -85.649556;
  arrWeights_0[14] = 188.245746;
  arrWeights_0[15] = 13.625241;
  arrWeights_0[16] = 140.068697;
  arrWeights_0[17] = 113.333588;
  arrWeights_0[18] = 1109.223634;
  arrWeights_0[19] = -914.960872;
  arrWeights_0[20] = 370.023213;
  arrWeights_0[21] = -28.047387;
  arrWeights_0[22] = 37.325713;
  arrWeights_0[23] = -226.078175;
  arrWeights_0[24] = 60.824913;
  arrWeights_0[25] = -121.224500;
  arrWeights_0[26] = 79.775097;
  arrWeights_0[27] = 4.628429;
  arrWeights_0[28] = -100.672645;
  arrWeights_0[29] = 97.028201;
  arrWeights_0[30] = 61.150900;
  arrWeights_0[31] = -159.271792;
  arrWeights_0[32] = 84.118150;
  arrWeights_0[33] = -9.871616;
  arrWeights_0[34] = -48.235015;
  arrWeights_0[35] = 77.718511;
  arrWeights_0[36] = 661.558706;
  arrWeights_0[37] = -411.591646;
  arrWeights_0[38] = 104.349090;
  arrWeights_0[39] = -99.837271;
  arrWeights_0[40] = 126.601131;
  arrWeights_0[41] = 107.472254;
  arrWeights_0[42] = 126.603749;
  arrWeights_0[43] = -34.495625;
  arrWeights_0[44] = 20.977627;
  arrWeights_0[45] = 3.895680;
  arrWeights_0[46] = -82.885884;
  arrWeights_0[47] = -33.776714;
  arrWeights_0[48] = -16.655781;
  arrWeights_0[49] = 215.236352;
  arrWeights_0[50] = 115.607084;
  arrWeights_0[51] = -12.600847;
  arrWeights_0[52] = -80.679499;
  arrWeights_0[53] = 242.773251;
  arrWeights_0[54] = 1031.385537;
  arrWeights_0[55] = -949.294602;
  arrWeights_0[56] = 187.067463;
  arrWeights_0[57] = -314.642807;
  arrWeights_0[58] = 148.656175;
  arrWeights_0[59] = 284.020192;
  arrWeights_0[60] = 82.173674;
  arrWeights_0[61] = -77.626693;
  arrWeights_0[62] = 7.912648;
  arrWeights_0[63] = 60.166694;
  arrWeights_0[64] = 18.410549;
  arrWeights_0[65] = 17.391980;
  arrWeights_0[66] = 162.603490;
  arrWeights_0[67] = -98.845268;
  arrWeights_0[68] = 21.648969;
  arrWeights_0[69] = 68.573664;
  arrWeights_0[70] = -129.288722;
  arrWeights_0[71] = 311.319087;
  arrWeights_0[72] = 1163.309077;
  arrWeights_0[73] = -964.935997;
  arrWeights_0[74] = 666.962079;
  arrWeights_0[75] = -283.212348;
  arrWeights_0[76] = -29.072518;
  arrWeights_0[77] = -71.041083;
  arrWeights_0[78] = -13.587105;
  arrWeights_0[79] = -64.148158;
  arrWeights_0[80] = -18.222318;
  arrWeights_0[81] = -130.446917;
  arrWeights_0[82] = -10.890569;
  arrWeights_0[83] = 113.613551;
  arrWeights_0[84] = 217.753494;
  arrWeights_0[85] = -311.183572;
  arrWeights_0[86] = -56.482977;
  arrWeights_0[87] = -100.310448;
  arrWeights_0[88] = 154.552381;
  arrWeights_0[89] = 137.994071;
  arrWeights_0[90] = 628.601433;
  arrWeights_0[91] = -415.789884;
  arrWeights_0[92] = 240.128736;
  arrWeights_0[93] = -189.593173;
  arrWeights_0[94] = 178.714259;
  arrWeights_0[95] = -72.434362;
  arrWeights_0[96] = -59.939457;
  arrWeights_0[97] = 1.622620;
  arrWeights_0[98] = -1.326195;
  arrWeights_0[99] = -12.406768;
  arrWeights_0[100] = -5.406124;
  arrWeights_0[101] = 11.512542;
  arrWeights_0[102] = 69.819580;
  arrWeights_0[103] = -49.454609;
  arrWeights_0[104] = 70.370092;
  arrWeights_0[105] = -5.417317;
  arrWeights_0[106] = 53.301957;
  arrWeights_0[107] = 212.577783;
  arrWeights_0[108] = 489.747638;
  arrWeights_0[109] = -47.747579;
  arrWeights_0[110] = 356.355751;
  arrWeights_0[111] = 12.281976;
  arrWeights_0[112] = 83.086865;
  arrWeights_0[113] = -22.621086;
  arrWeights_0[114] = 85.532027;
  arrWeights_0[115] = -8.316129;
  arrWeights_0[116] = -22.075525;
  arrWeights_0[117] = -5.974693;
  arrWeights_0[118] = 53.147061;
  arrWeights_0[119] = -9.791896;
  arrWeights_0[120] = 124.374967;
  arrWeights_0[121] = 94.065214;
  arrWeights_0[122] = 74.463043;
  arrWeights_0[123] = -53.090217;
  arrWeights_0[124] = -197.958978;
  arrWeights_0[125] = 610.478372;
  arrWeights_0[126] = 872.953472;
  arrWeights_0[127] = -454.008789;
  arrWeights_0[128] = 316.138742;
  arrWeights_0[129] = -53.098020;
  arrWeights_0[130] = -57.396066;
  arrWeights_0[131] = -231.539769;
  arrWeights_0[132] = 65.593983;
  arrWeights_0[133] = 98.067651;
  arrWeights_0[134] = 83.214865;
  arrWeights_0[135] = -135.525130;
  arrWeights_0[136] = -287.146326;
  arrWeights_0[137] = 2.720263;
  arrWeights_0[138] = 68.797985;
  arrWeights_0[139] = -139.371165;
  arrWeights_0[140] = -158.121714;
  arrWeights_0[141] = -146.721476;
  arrWeights_0[142] = 110.268264;
  arrWeights_0[143] = -183.986283;
  arrWeights_0[144] = 875.275098;
  arrWeights_0[145] = -178.004881;
  arrWeights_0[146] = 126.398123;
  arrWeights_0[147] = -194.081012;
  arrWeights_0[148] = 6.610336;
  arrWeights_0[149] = -87.990637;
  arrWeights_0[150] = 13.705421;
  arrWeights_0[151] = 44.991572;
  arrWeights_0[152] = 125.759172;
  arrWeights_0[153] = -122.537789;
  arrWeights_0[154] = -3.030823;
  arrWeights_0[155] = -36.755244;
  arrWeights_0[156] = -109.081299;
  arrWeights_0[157] = 13.706895;
  arrWeights_0[158] = -30.721436;
  arrWeights_0[159] = -71.412672;
  arrWeights_0[160] = 77.002588;
  arrWeights_0[161] = 423.219544;
  arrWeights_0[162] = 332.483426;
  arrWeights_0[163] = 2.666241;
  arrWeights_0[164] = 49.111954;
  arrWeights_0[165] = 25.015117;
  arrWeights_0[166] = 64.493639;
  arrWeights_0[167] = 7.458690;
  arrWeights_0[168] = -2.371083;
  arrWeights_0[169] = -15.076639;
  arrWeights_0[170] = -45.258685;
  arrWeights_0[171] = -16.860716;
  arrWeights_0[172] = -21.124351;
  arrWeights_0[173] = 24.885828;
  arrWeights_0[174] = -8.689909;
  arrWeights_0[175] = 2.180072;
  arrWeights_0[176] = 19.545566;
  arrWeights_0[177] = 23.089228;
  arrWeights_0[178] = 0.903966;
  arrWeights_0[179] = 51.994202;
  arrWeights_0[180] = 538.316425;
  arrWeights_0[181] = -263.600630;
  arrWeights_0[182] = 163.742209;
  arrWeights_0[183] = -120.650675;
  arrWeights_0[184] = 32.008412;
  arrWeights_0[185] = -21.026106;
  arrWeights_0[186] = -70.434916;
  arrWeights_0[187] = -16.096585;
  arrWeights_0[188] = 38.115713;
  arrWeights_0[189] = 27.837031;
  arrWeights_0[190] = 5.112014;
  arrWeights_0[191] = 32.051937;
  arrWeights_0[192] = -25.332025;
  arrWeights_0[193] = -70.777686;
  arrWeights_0[194] = 67.580428;
  arrWeights_0[195] = -9.746388;
  arrWeights_0[196] = -94.906632;
  arrWeights_0[197] = 166.774521;
  arrWeights_0[198] = 457.039764;
  arrWeights_0[199] = -245.320305;
  arrWeights_0[200] = 125.145628;
  arrWeights_0[201] = -90.931176;
  arrWeights_0[202] = 42.628177;
  arrWeights_0[203] = -48.379481;
  arrWeights_0[204] = -14.323804;
  arrWeights_0[205] = -32.374030;
  arrWeights_0[206] = 33.615677;
  arrWeights_0[207] = 46.242721;
  arrWeights_0[208] = 40.078101;
  arrWeights_0[209] = 56.229875;
  arrWeights_0[210] = -58.679682;
  arrWeights_0[211] = -75.272244;
  arrWeights_0[212] = 26.249448;
  arrWeights_0[213] = -36.083065;
  arrWeights_0[214] = -148.545840;
  arrWeights_0[215] = 69.829234;
  arrWeights_0[216] = 473.057597;
  arrWeights_0[217] = -182.640811;
  arrWeights_0[218] = 193.630779;
  arrWeights_0[219] = -39.101108;
  arrWeights_0[220] = 105.989570;
  arrWeights_0[221] = -34.845741;
  arrWeights_0[222] = 11.396567;
  arrWeights_0[223] = 7.375632;
  arrWeights_0[224] = 67.638279;
  arrWeights_0[225] = 18.946478;
  arrWeights_0[226] = -38.825600;
  arrWeights_0[227] = 19.639095;
  arrWeights_0[228] = -12.276608;
  arrWeights_0[229] = -41.479370;
  arrWeights_0[230] = 16.967973;
  arrWeights_0[231] = -26.950883;
  arrWeights_0[232] = -118.804194;
  arrWeights_0[233] = 74.012673;
  arrWeights_0[234] = 427.365549;
  arrWeights_0[235] = -7.130010;
  arrWeights_0[236] = -46.428358;
  arrWeights_0[237] = -100.177396;
  arrWeights_0[238] = -69.269862;
  arrWeights_0[239] = -42.469613;
  arrWeights_0[240] = -79.125184;
  arrWeights_0[241] = 32.714942;
  arrWeights_0[242] = 46.045283;
  arrWeights_0[243] = 71.389496;
  arrWeights_0[244] = 11.366095;
  arrWeights_0[245] = 35.890181;
  arrWeights_0[246] = 1.288889;
  arrWeights_0[247] = -36.168873;
  arrWeights_0[248] = 16.094410;
  arrWeights_0[249] = -8.426539;
  arrWeights_0[250] = -12.246986;
  arrWeights_0[251] = -11.679921;
  arrWeights_0[252] = 652.368249;
  arrWeights_0[253] = -281.203628;
  arrWeights_0[254] = 143.988766;
  arrWeights_0[255] = -206.389441;
  arrWeights_0[256] = 62.966834;
  arrWeights_0[257] = -41.260767;
  arrWeights_0[258] = 92.435259;
  arrWeights_0[259] = -34.447151;
  arrWeights_0[260] = -7.033507;
  arrWeights_0[261] = -67.193541;
  arrWeights_0[262] = 31.377436;
  arrWeights_0[263] = 95.665229;
  arrWeights_0[264] = 111.691227;
  arrWeights_0[265] = 8.980044;
  arrWeights_0[266] = -52.609443;
  arrWeights_0[267] = 44.082103;
  arrWeights_0[268] = 117.037182;
  arrWeights_0[269] = 361.990866;
  arrWeights_0[270] = 87.151436;
  arrWeights_0[271] = 67.855842;
  arrWeights_0[272] = 6.381954;
  arrWeights_0[273] = -7.413551;
  arrWeights_0[274] = -0.082699;
  arrWeights_0[275] = 7.611933;
  arrWeights_0[276] = 20.165410;
  arrWeights_0[277] = 25.353867;
  arrWeights_0[278] = 19.472293;
  arrWeights_0[279] = 47.533612;
  arrWeights_0[280] = 67.126236;
  arrWeights_0[281] = 57.354203;
  arrWeights_0[282] = 7.180279;
  arrWeights_0[283] = 0.252334;
  arrWeights_0[284] = -23.811531;
  arrWeights_0[285] = -8.133957;
  arrWeights_0[286] = 54.602033;
  arrWeights_0[287] = 32.664639;
  arrWeights_0[288] = 781.454739;
  arrWeights_0[289] = -607.609400;
  arrWeights_0[290] = 310.311980;
  arrWeights_0[291] = -72.538665;
  arrWeights_0[292] = 163.796243;
  arrWeights_0[293] = -96.205120;
  arrWeights_0[294] = 62.424626;
  arrWeights_0[295] = -30.809910;
  arrWeights_0[296] = -124.474945;
  arrWeights_0[297] = -241.936656;
  arrWeights_0[298] = -143.034299;
  arrWeights_0[299] = -27.625224;
  arrWeights_0[300] = 314.000569;
  arrWeights_0[301] = 142.585012;
  arrWeights_0[302] = 19.438387;
  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[3] = 7.247761;
  arrWeights_1[4] = 5.400128;
  arrWeights_1[5] = 4.255944;
  arrWeights_1[6] = 4.830370;
  arrWeights_1[7] = 6.498321;
  arrWeights_1[8] = 4.329279;
  arrWeights_1[9] = 21.221723;
  arrWeights_1[10] = 6.657713;
  arrWeights_1[11] = 4.870042;
  arrWeights_1[12] = 4.729068;
  arrWeights_1[13] = 17.062403;
  arrWeights_1[14] = 1.043106;
  arrWeights_1[15] = -14.022122;
  arrWeights_1[16] = 8.032901;
  arrWeights_1[17] = 32.278722;
  arrWeights_1[18] = 6.104636;
  arrWeights_1[19] = 5.363750;
  arrWeights_1[20] = 8.564406;
  arrWeights_1[21] = 7.089320;
  arrWeights_1[22] = 5.438834;
  arrWeights_1[23] = 4.473877;
  arrWeights_1[24] = 4.318535;
  arrWeights_1[25] = 6.570244;
  arrWeights_1[26] = 4.368093;
  arrWeights_1[27] = 20.619950;
  arrWeights_1[28] = 6.481450;
  arrWeights_1[29] = 4.540840;
  arrWeights_1[30] = 4.636043;
  arrWeights_1[31] = 16.533273;
  arrWeights_1[32] = 0.861578;
  arrWeights_1[33] = -14.068211;
  arrWeights_1[34] = 8.158290;
  arrWeights_1[35] = 31.833575;
  arrWeights_1[36] = 8.734738;
  arrWeights_1[37] = 10.190998;
  arrWeights_1[38] = 8.430947;
  arrWeights_1[39] = 8.285439;
  arrWeights_1[40] = 11.494198;
  arrWeights_1[41] = 9.383911;
  arrWeights_1[42] = 15.227542;
  arrWeights_1[43] = 9.827119;
  arrWeights_1[44] = 16.361780;
  arrWeights_1[45] = -17.675840;
  arrWeights_1[46] = 4.608113;
  arrWeights_1[47] = 6.859722;
  arrWeights_1[48] = 6.597408;
  arrWeights_1[49] = -9.915901;
  arrWeights_1[50] = 13.741963;
  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);
}

The code should look familiar, all I did was re-writing it, using slightly different language syntax of MQL.

This indicator has two buffers, and draws two lines, one for the original NOC, and one for the NN-predicted NOC. For trading, you don't have to draw both indicator lines, of course (see MQL tutorials to learn how to do it), but I have decided to show them together, so you can compare.

Another difference, that you should know about, is the way MT performs testing. It may, in some cases, be more accurate, then one we did (we did the worse case scenario). Of course, you can always to change the SLANG script from the examples above, to implement any logic you want.

The result of our testing in MT is a bit better, then in Cortex, due to all these reasons.

Keep in mind,that MT calculates the DD in a different way. I still think, that my way is better.

In should be especially noted, that no additional optimization had been performed using MetaTrader's optimizer. We have just plugged our MTS (mechanical trading system) in, and it worked as expected.


That is it. You can now create Cortex Neural Network, optimize it to do trading, and to port it to the trading platform of your choice.







(C) snowcron.com, all rights reserved

Please read the disclaimer