WiseTrader Toolbox

Running a Network on a Chart

The worked tutorials train and run a network in a single formula so you see a result straight away. That is the right choice in the Analysis window, where a formula runs once. On a chart it is the wrong choice. This page explains why, and gives you a formula that behaves itself: it trains only when you click a button, and once trained it plots its prediction automatically. The example is Tutorial 1's return predictor — the same network, made comfortable to leave open on a chart.

Note

A ready-to-run copy is installed with the toolbox at WiseTraderToolbox\Neural Networks\Neural Network On A Chart.afl. Open it, insert it on a chart, and click Train network.

Why a chart is different

AmiBroker re-runs a chart indicator on almost every interaction — scrolling, zooming, a click, moving the crosshair, changing the symbol or the interval. Each of those is a recalc. A formula that trains inline therefore retrains on every one of them. And training blocks: the formula does not return until training finishes, so the pane cannot paint in the meantime. Put together, an inline-training formula on a chart freezes and retrains endlessly.

Running an already-trained network is the opposite: one forward pass per bar, fast enough to do on every recalc without anyone noticing. So the whole technique is just to separate the two — train rarely and deliberately, predict freely.

Train on a click, plot on every recalc

Training goes behind a button and happens only when you click it. Prediction runs on every recalc, but only once a trained network exists. The formula works out which of two states it is in by looking on disk:

  • No saved network yet — it shows a Train button and a centred message, "The neural network has not been trained yet." Nothing is run or plotted.
  • A saved network exists — it shows a Retrain button and plots the saved network's prediction automatically. Prediction is cheap, so there is no plot button and nothing to toggle.

The state is read from disk rather than held in a variable because it must survive closing and reopening AmiBroker. A StaticVar would be forgotten on restart; the saved file is not.

The worked example

Here is the whole formula. Read it top to bottom — the comments walk through each section — then the parts that matter are pulled out below.

//  WiseTrader Toolbox - Running a Neural Network on a Chart
//  --------------------------------------------------------------------------
//  Tutorial 1's return predictor, rebuilt so it is comfortable to leave on a
//  chart. Training inline is fine in the Analysis window, where a formula runs
//  once, but it is painful on a chart: AmiBroker re-runs a chart indicator on
//  almost every interaction - scroll, zoom, click, crosshair move, a change of
//  symbol or interval. Training blocks until it finishes, so a formula that
//  trains inline retrains on every one of those events and the pane freezes.
//
//  The fix: train ONLY when you click the button. Running an already-trained
//  network is cheap, so once a saved network exists the prediction is plotted
//  automatically on every recalc. Whether a network exists is read from disk
//  (an fopen check on the saved file), so it survives an AmiBroker restart.
//  --------------------------------------------------------------------------

// 1. Settings - the same recipe as Tutorial 1
SetBarsRequired(99999, 99999);
SetLearningAlgorithm(4);                     // iRPROP+ : robust, no learning rate to tune
SetNetworkWithActivationLayer1(8, 1, 9);     // 8 tanh hidden neurons, linear output
SetErrorAlgorithm(0);                        // plain regression error
SetScalingAlgorithm(0);                      // mean / standard-deviation scaling
SetPercentTestingData(25);                   // hold back the most recent 25% as unseen test data
SetEarlyStoppingPatience(30);
SetMaximumEpochs(300);
SetSeed(1);

// The saved network. The plugin writes the name you pass VERBATIM into
// WiseTraderToolbox\NeuralNetwork\ - it does NOT add a .net extension, so the
// fopen and fdelete paths below must use the exact same name.
netName = Name() + "_ChartReturn";
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 button. A click on the chart triggers a recalc, so the training in
//    section 5 runs on the click itself - no RequestMouseMoveRefresh needed.
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. This blocks once, saves the network, then clears
//    the training input set so it can never collide with the run set below.
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);
    AddNeuralNetworkOutput(Ref(ROC(C, 1), 1), 0);     // target: the next bar's return

    fdelete(netPath);                                 // drop any stale network so we retrain fresh
    TrainMultiInputNeuralNetwork(netName);
    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);                                   // close every handle fopen returns (or Error 53)
    hasNet = True;
}

// 6. Display. The prediction is cheap, so it runs on every recalc once trained.
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);          // turn the predicted return back into a price
    Plot(predClose, "Predicted next close", colorRed, styleLine, Null, Null, 1);  // shift +1: it predicts the next bar

    Title = "Neural network on a chart - predicted next close = " + predClose;
}
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 = "Neural network on a chart - not trained yet";
}

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

Detecting the network on disk

The plugin saves a trained network under the exact name you pass to TrainMultiInputNeuralNetwork, inside WiseTraderToolbox\NeuralNetwork\. It does not add an extension — the name is used verbatim — so the name we train under, the file we look for, and the file we delete before retraining are all the same string. We test for it with fopen in read mode; a valid handle means the file is there, and we close it again straight away. Close every handle fopen hands back — leave one open and AmiBroker stops the formula with Error 53.

Warning

Do not bolt a .net (or any) extension onto the fopen path unless you also pass that exact name to TrainMultiInputNeuralNetwork. The two must match character for character, or the formula will train a network it can never find again and will always think it is untrained.

The button

DrawButton is a small helper: it draws a gradient rectangle and centres a caption inside it. GetCursorMouseButtons() returns 9 when the left button is down and the cursor is over this pane; GetCursorXPosition(1) and GetCursorYPosition(1) give the cursor position in pixels, which is what we test against the button rectangle. A click on the chart is itself a recalc, so the training fires on the click — you do not need RequestMouseMoveRefresh. The only thing that differs between the two states is the label: Train when there is no network, Retrain when there is.

Keeping the two input sets apart

This is the part to get right. A network formula builds an input set with AddNeuralNetworkInput / AddNeuralNetworkOutput, and both the trainer and the runner act on whatever set is current. The training set (the inputs and the target) and the prediction set (the same inputs, no target) must never be mixed, or the runner sees the wrong number of channels. ClearNeuralNetworkInputs() keeps them apart:

  • the training set is built, trained on and cleared entirely inside the if(BtnClicked) block;
  • the prediction set is built and run inside the if(hasNet) block;
  • a clear at the top and a clear in the cleanup bracket the whole formula.

Because the training set is torn down before the prediction set is built, the two can never collide. Note that every AddNeuralNetworkInput and AddNeuralNetworkOutput carries the trailing , 0 index — the multi-input functions require it.

The "not trained yet" message

When there is no network, instead of plotting we draw one line of text in the middle of the pane. Status("pxwidth") and Status("pxheight") give the pane's size in pixels; drawing the text into a rectangle that fills the pane, with the centring flags, puts it dead centre.

Two finishing touches

Note

The two error numbers (NeuralNetworkMSE and TestingDataNeuralNetworkMSE) are published only while training runs, so they are meaningful right after a click and not between trainings. If you want them on screen permanently, save them to a StaticVar inside the train block and read them back into the title. Tutorial 1 explains why those two numbers are the ones to watch.

Tip

Because training blocks, a single click produces a single training pass — by the time the chart recalculates again the click is long over — so the button does not need debouncing. If you want to be strict about it, remember the button state between recalcs and fire only on the press edge:

key   = "WTT_NN_btn_" + GetChartID() + Name();
wasDn = Nz(StaticVarGet(key));
StaticVarSet(key, LBClick);
BtnClicked = CursorInBtn AND LBClick AND NOT wasDn;

The key is built from GetChartID() and the symbol, so each pane tracks its own button.

Using it for the other tutorials

The skeleton is not specific to return prediction — all four tutorials now ship in exactly this form, and the only parts that differ are the Settings block and the input/target blocks. Tutorial 2's classifier plots its 0–1 score in its own pane instead of a price; Tutorial 3's LSTM plots a predicted move; Tutorial 4 trains its filter on a chart and then loads it in the Analysis-window backtest. The button, the disk check and the centred message stay exactly the same.