WiseTrader Toolbox

Tutorial 1: Predicting the Next Return

This is the first of three worked tutorials. It builds a simple feed-forward network that predicts the next bar's move, and along the way it shows the habits that matter far more than any clever setting: feeding the network data it can actually learn from, and watching for overfitting from the very first run. If you have not yet read the Introduction, skim it first — this page assumes you know what training and prediction are.

Note

A ready-to-run copy of the worked example below is installed with the toolbox at WiseTraderToolbox\Neural Networks\Tutorial 1 - Return Prediction.afl. Open it, insert it on a chart, and you have the whole tutorial running.

The shape of every network formula

Almost every network formula you write has the same three sections, and keeping to that shape makes them easy to read:

  1. Settings — the Set… functions that configure the network. These must come before training.
  2. Inputs, output, train and run — you build your input arrays and the target to train, and build the inputs again (without the target) to run the saved network.
  3. CleanupRestoreDefaults() and ClearNeuralNetworkInputs() so the next formula starts clean.

With the Standard network functions, training and prediction are two separate jobs: you train and save a network once, then load and run it as often as you like. The example keeps both in one file but puts training behind a button, so on a chart it trains only when you click and otherwise just runs the saved network. We come back to the Standard-versus-Walk-Forward choice at the end.

Predict a return, not a price

The single most useful habit in this whole manual: do not feed the network raw price, and do not ask it to predict raw price. A stock that traded at $2 a decade ago and $40 today has no stable range, so a network trained on its prices has nothing steady to latch onto. The fix is to work in rate of change — the percentage move from one bar to the next. A return stays in roughly the same range across the entire history, which is what the network needs. This idea (stationarity) is covered more fully on the Input & Output Scaling page.

So instead of predicting tomorrow's close directly, we predict tomorrow's return and then add it back onto today's close to recover a predicted price. The inputs are returns too: this bar's return and a few lagged returns, plus one momentum gauge (RSI) that is already in a tidy 0–100 range.

The worked example

Here is the complete formula. It shows a Train button; click it once to train and save the network, after which it plots the predicted next close on top of the price automatically on every recalc. Read it once top to bottom — the comments walk through each section — then we will pull out the parts that matter.

//  WiseTrader Toolbox - Neural Network Tutorial 1: Return Prediction
//  --------------------------------------------------------------------------
//  Predicts the NEXT bar's percentage return with a feed-forward MLP, then adds
//  the predicted return back onto today's close to get a predicted price.
//  We predict a RETURN (rate of change) rather than the raw price because a
//  return stays in a stable range over the whole history, which is what a
//  neural network needs to learn from.
//
//  Training runs ONLY when you click the Train button, so this is happy left on
//  a chart - it does not retrain on every scroll or click. Once a network is
//  saved it plots the predicted close automatically on every recalc. The button
//  technique is explained in full under "Running a Network on a Chart".
//  --------------------------------------------------------------------------

SetBarsRequired(99999, 99999);

// 1. Settings
SetLearningAlgorithm(4);                     // iRPROP+ : robust, no learning rate to tune
SetNetworkWithActivationLayer1(8, 1, 9);     // 8 tanh hidden neurons, linear output
SetErrorAlgorithm(0);                        // plain regression error (the default is tanh!)
SetScalingAlgorithm(0);                      // mean / standard-deviation scaling
SetPercentTestingData(25);                   // hold back the most recent 25% as unseen test data
SetEarlyStoppingPatience(30);                // stop once the test error stops improving
SetMaximumEpochs(300);
SetSeed(1);                                  // repeatable runs

netName = Name() + "_T1_Return";
netPath = "WiseTraderToolbox\\NeuralNetwork\\" + netName;

ClearNeuralNetworkInputs();

// 2. Has this symbol's network been trained yet? Ask the disk.
hasNet = False;
fh = fopen(netPath, "r");
if(fh)
{
    fclose(fh);                                  // close every handle fopen returns (or Error 53)
    hasNet = True;
}

// 3. The Train / Retrain button. A click on the chart triggers a recalc, so the
//    training in section 4 runs on the click itself.
function DrawButton(Text, x1, y1, minW, h, colorFrom, colorTo)
{
    GfxSetOverlayMode(0);
    GfxSelectFont("Segoe UI", 9, 700);           // pick the font BEFORE measuring the caption
    GfxSetBkMode(1);
    padX = 12;                                    // space to leave on each side of the caption
    w    = Max(minW, GfxGetTextWidth(Text) + 2 * padX);   // widen the button if the label is long
    GfxGradientRect(x1, y1, x1 + w, y1 + h, colorFrom, colorTo);
    GfxSetTextColor(colorWhite);                 // white caption on the green box - reads on any theme
    GfxDrawText(Text, x1, y1, x1 + w, y1 + h, 32|4|1);    // single line, centred horizontally + vertically
    return w;
}

BtnX = 5;
BtnY = 40;                                   // pushed down so it clears the chart title
BtnW = 170;
BtnH = 30;
BtnTop = ColorRGB(59, 130, 246);             // button gradient - lighter blue on top ...
BtnBot = ColorRGB(37, 99, 235);              // ... deepening to a darker blue at the bottom
LBClick = GetCursorMouseButtons() == 9;      // 9 = left button down AND cursor over this pane
MouseX  = Nz(GetCursorXPosition(1));          // pixel coordinates
MouseY  = Nz(GetCursorYPosition(1));

if(hasNet)
    BtnLabel = "Retrain network";
else
    BtnLabel = "Train network";

BtnW = DrawButton(BtnLabel, BtnX, BtnY, BtnW, BtnH, BtnTop, BtnBot);   // BtnW becomes the width actually drawn
CursorInBtn = MouseX >= BtnX AND MouseX <= BtnX + BtnW AND MouseY >= BtnY AND MouseY <= BtnY + BtnH;
BtnClicked  = CursorInBtn AND LBClick;

// 4. TRAIN - only on a click. Inputs are this bar's return plus a few lagged
//    returns (all stationary), and a momentum gauge. Target: the NEXT bar's return.
if(BtnClicked)
{
    ClearNeuralNetworkInputs();
    AddNeuralNetworkInput(ROC(C, 1), 0);
    for(i = 1; i <= 5; i++)
        AddNeuralNetworkInput(Ref(ROC(C, 1), -i), 0);
    AddNeuralNetworkInput(RSI(14) / 100, 0);     // momentum, already 0..1
    AddNeuralNetworkOutput(Ref(ROC(C, 1), 1), 0);

    fdelete(netPath);                            // drop any stale network so we retrain fresh
    TrainMultiInputNeuralNetwork(netName);
    StaticVarSetText("WTT_" + netName, "Training MSE = " + NeuralNetworkMSE +
                     "   Test MSE = " + TestingDataNeuralNetworkMSE);
    ClearNeuralNetworkInputs();
}

// 5. Re-read the disk after training: hasNet is true only if the save worked.
hasNet = False;
fh = fopen(netPath, "r");
if(fh)
{
    fclose(fh);
    hasNet = True;
}

// 6. Display. Running a trained network is cheap, so it runs on every recalc.
Plot(C, "Close", colorDefault, styleCandle);

if(hasNet)
{
    ClearNeuralNetworkInputs();                  // run set: same inputs, index 0, NO output
    AddNeuralNetworkInput(ROC(C, 1), 0);
    for(i = 1; i <= 5; i++)
        AddNeuralNetworkInput(Ref(ROC(C, 1), -i), 0);
    AddNeuralNetworkInput(RSI(14) / 100, 0);

    predReturn = RunMultiInputNeuralNetwork(netName);
    predClose  = C * (1 + predReturn / 100);     // add the predicted return back onto today's close
    Plot(predClose, "Predicted next close", colorRed, styleLine, Null, Null, 1);

    Title = "Tutorial 1 - predicted next close = " + predClose + "      " +
            StaticVarGetText("WTT_" + netName);
}
else
{
    // White text over a black outline, so it stays readable whether the chart
    // background is white or black.
    msg = "The neural network has not been trained yet.";
    pw  = Status("pxwidth");
    ph  = Status("pxheight");
    GfxSetOverlayMode(0);
    GfxSelectFont("Segoe UI", 12, 700);
    GfxSetBkMode(1);
    GfxSetTextColor(colorBlack);
    GfxDrawText(msg,  1, 0, pw + 1, ph,     32|1|4|16);
    GfxDrawText(msg, -1, 0, pw - 1, ph,     32|1|4|16);
    GfxDrawText(msg, 0,  1, pw,     ph + 1, 32|1|4|16);
    GfxDrawText(msg, 0, -1, pw,     ph - 1, 32|1|4|16);
    GfxSetTextColor(colorWhite);
    GfxDrawText(msg, 0,  0, pw,     ph,     32|1|4|16);

    Title = "Tutorial 1 - click Train to train the network";
}

// 7. Cleanup
EnableProgress();
RestoreDefaults();
ClearNeuralNetworkInputs();
Tip

The button is what lets this formula live on a chart. A chart indicator recalculates on every scroll, zoom and click, so training inline would retrain endlessly and freeze the pane; gating training behind a click trains once and then just plots the saved network. The mechanism — the disk check, the click handling and the input-set sequencing — is explained step by step under Running a Network on a Chart.

The inputs and the target

We use the flexible multi-input API here (AddNeuralNetworkInput / AddNeuralNetworkOutput) because it lets a for loop add as many lagged returns as we like without counting arguments. Every input is a return, so every input is stationary.

The target is Ref( ROC(C, 1), 1 ) — the return one bar into the future. Looking one bar ahead is fine here: it is only the target we are reaching into the future for, which is what we want the network to learn. The inputs all look backwards (Ref(…, -i)), so nothing the network sees at prediction time depends on data it would not have yet.

Warning

Never let an input peek at the future. An input built with a positive Ref shift, or an indicator that re-centres using later bars, leaks tomorrow's answer into today's prediction. The network will look brilliant in the test and fail completely in real time. Keep every input strictly backward-looking.

The settings that matter

SettingWhy it is there
SetLearningAlgorithm(4)iRPROP+, the robust "set and forget" optimizer. It ignores the learning rate and trains well on the usual full-batch data. See Training Algorithms.
SetNetworkWithActivationLayer1(8, 1, 9)One hidden layer of 8 tanh neurons, then a linear output (code 9). A return is unbounded, so the output must be linear — a sigmoid would squash it into 0–1.
SetErrorAlgorithm(0)Plain regression error. This matters: the default loss is tanh (meant for classification), so for any regression you must switch it to 0.
SetPercentTestingData(25)Holds back the most recent 25% of bars as unseen test data, and switches training to keep the network that scores best on that test data. This one line does more for real-world accuracy than anything else.
SetEarlyStoppingPatience(30)Stops training once the test error has not improved for 30 epochs, so the network can't keep grinding away memorising noise.
Tip

SetScalingAlgorithm(0) selects mean / standard-deviation scaling, which suits returns well. (The AFL code is the reverse of what you might guess: 0 is mean/standard-deviation and 1 is min/max.)

Read the two error numbers every time

After training, the toolbox publishes two numbers, and the example prints both in the chart title:

  • NeuralNetworkMSE — the error on the training data.
  • TestingDataNeuralNetworkMSE — the error on the held-out test data (only produced because we reserved test data).

Get into the habit of comparing them on every single run. If the training error is low but the test error is much higher, the network has overfit — it has memorised the training bars instead of learning a pattern that carries forward. That is the only error that matters for trading, and the whole of the Accuracy & Overfitting page is about getting it down. If the two numbers are close, you can trust the result a lot more.

Standard or Walk-Forward?

The example above is a Standard network: you train it, save it under a name, and run it. That is the most flexible path and the only one that supports the recurrent models in Tutorial 3.

The other path is a Walk-Forward network. The NeuralNetworkIndicator… functions train and predict in a single call: as the indicator walks along the chart it retrains a fresh short-lived network on the bars just before each bar and predicts ahead. Because every prediction only ever sees past data, the output is genuinely out-of-sample at each bar — a realistic picture of live behaviour, at the cost of being heavier to compute. The same return-prediction idea looks like this as a Walk-Forward indicator:

SetBarsRequired(99999, 99999);
SetNetworkWithActivationLayer1(8, 1, 9);   // tanh hidden, linear output
SetErrorAlgorithm(0);
SetMaximumEpochs(200);

input1 = ROC(C, 1);
input2 = Ref(ROC(C, 1), -1);
target = ROC(C, 1);                        // the indicator predicts DaysAhead bars forward itself
pred   = NeuralNetworkIndicator3(input1, input2, target, Name(), 200, 1);

predClose = C * (1 + pred / 100);
Plot(predClose, "Walk-forward predicted close", colorBlue, styleLine);

RestoreDefaults();
ClearNeuralNetworkInputs();

The suffix in NeuralNetworkIndicator3 is the number of arrays you pass — two inputs plus one output — followed by a data name, a look-back length, and how many bars ahead to predict. The full family is documented under Neural Network Functions.

Be honest about the result

Run the example and you will notice the predicted close usually sits very near today's close. That is not a bug — it is the market telling you the truth. The next bar's return is mostly noise, and a handful of recent returns carry only a faint signal, so the safest prediction really is "about the same". Squeezing more out of it is a matter of finding inputs that genuinely carry information (other markets, sector indices, volume behaviour, longer context) rather than turning up the epochs or piling on neurons — that mostly buys you overfitting. Tutorial 2 reframes the problem as an easier question — just up or down — and adds a fuller set of overfitting controls.