An example of using Neural Networks for stock and Forex trading.
|
|
Neural Networks and Stock / Forex TradingAn 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
AboutIn 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.
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.
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).
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
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).
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
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
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
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
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
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 notesFirst 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:
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 withFirst 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
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
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:
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
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
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
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
The TeachNn function simply brings up the NN dialog. forex_nn_01.tsc, part 7
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
Compile and Run the script. Well... As expected, using 7 hours as an interval for the CLV produced very poor results:
Optimization - part 2What 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. 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
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
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
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
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
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 <
The reason is, we don't have the "future" data for the last elements.
However, we can use the NN to create a prediction.
|