Neural Networks and Stock / Forex Trading

An example of using Neural Networks for stock and Forex trading.

Visibility is very important for this site. If you like it please link to this URL or use our online form to add your reciprocal link. Learn more about reciprocal links


Neural Networks and Stock / Forex Trading

An example of using the Cortex Feedforward Backpropagation Neural Network Application.

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

Contents

About

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

You will learn how to choose inputs for the Neural Network, 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 optimization of both the Neural Network (number of neurons) and the trading parameters (stop loss etc.)

Finally (the part that is not present in most tutorials), you will learn what to do next. After all, Cortex cannot do real time trading, you need to use somrething like Trade Station, MetaStocks or MetaTrader. How to port the Neural Network from Cortex to your favorite trading platform? Do you have to deal with DLLs, ActiveX controls and low-level programming? The answer is NO.
Cortex comes with the easy to use feature that allows you to easily port the resulting (trained) 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, but you still need to invent your own trading system. The one we use here is barely a starting point, and shouldn't be used for Forex trading "as is". The idea of this text is to teach you to create 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 is using examples, lots of them. To make your life easier, I have included them all, not just fragments. However it makes the text much longer. Also, I am going from the very first, clumsy, trading systems, to more advanced, every time explaining what had been improved and why. Be patient, or jump directly to the section you need.

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

What is wrong with the "simple" examples?

In the Cortex user's guide we used a simple example of a 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 NN can predict future values, on the first place?"

The answer is: it is learning 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 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 stock price prediction! We do not care! What we need is a trading signal.

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 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, 0, "", 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. Nevertheless, just for the sake of an exercise, we are not using "reverse" parameters of TABLE_LOADER, instead, we will reverse arrays "by hand".

stocks_nn.tsc, part 2

    // Reversing the arrays
    double nArraySize = ARRAY_SIZE(arrClose) / 2 + 1;

    for(i = 0; i < nArraySize; i = i + 1)
    {
        arrClose[i] = arrClose[nArraySize - i - 1];
        arrLow[i] = arrLow[nArraySize - i - 1];
        arrHigh[i] = arrHigh[nArraySize - i - 1];
    }

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 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 programming, let's use SLANG 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.

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

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 mater 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 trading system 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 algorythm 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 algorythm 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 mater, 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:

Optimization - part 2

What to optimize?

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?

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.

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.

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 the XML tutorial 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.

Trading with Neural Networks

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

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 NN 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,