Neural Networks Without Magic: Occam’s Razor from a Programmer’s Desk

AI doesn’t have to feel mystical. High-school math, a few derivatives, and clean C++/MQL5 code are enough. No spells—just matrices, functions, and discipline. This is how I build it in practice.


What you actually need

Neural-network articles often add fog. I use Occam’s razor and keep the essentials—the stuff most of us remember from school:

  • multiplication and addition,
  • simple nonlinearities (tanh, sigmoid, ReLU),
  • basic vector and matrix work,
  • and derivatives for learning (backpropagation).

That’s it. Everything else is convenience, not necessity.


Derivatives: the only “mandatory optional”

Without derivatives you get a forward pass and a number. Learning needs a direction. The chain rule turns error into updates: compute the loss, send it back through the layers, adjust weights and biases using derivatives of the activations and the linear parts.

  • Error: prediction vs. reality (MSE).
  • Gradient: derivative of a composite function (chain rule).
  • Update: a small step that reduces the error (SGD).

If you can differentiate sigmoid, tanh and the ReLU “derivative”, you’ve covered 99% of day-to-day work.


C++ and MQL5: from math to a running chart

In C++ I use a small DLL: dense layers, activations, forward/backprop, single-sample and mini-batch training. A plain C API (handle-based, no global singletons). In MQL5, the EA takes data straight from the chart, normalizes it, feeds the network, and draws the outputs.

  • y = f(Wx + b) — forward pass.
  • dL/dW, dL/db — gradients via activation derivatives.
  • SGD, MSE, gradient clipping, He/Xavier init.
  • Batch wrappers for throughput (NN_ForwardBatch, NN_TrainBatch).
  • Weights access for tools (NN_GetWeights, NN_SetWeights).

The effect is simple: AI stops looking magical and turns back into engineering.


Occam, condensed

  • Forward: matrix × vector.
  • Activation: tanh/sigmoid/ReLU are ordinary functions.
  • Loss (MSE): mean of squared differences.
  • Backprop: just the chain rule.

Complexity comes from stacking simple parts, not from mystery.


What this approach gives me

I treat a network as a calculator, not an oracle. I know its limits and its strengths. And I own the toolchain: save/load weights in the host app, add Adam later, extend outputs, plug it into trading logic when needed.

High-school math, patience, and the habit of shaving off ballast go a long way.


Conclusion

Neural networks aren’t a black box. They’re numbers in matrices and a handful of derivatives—a mechanical recipe I write in the languages I enjoy: C++ and MQL5.


Download (EA / sources)

NNMQL5 — documentation:


A lightweight DLL with a C API for a simple MLP (a stack of dense layers):

  • forward inference (NN_Forward, NN_ForwardBatch)
  • training: per-sample and mini-batch (NN_TrainOne, NN_TrainBatch)
  • multiple networks managed by integer handles
  • weights read/write for tooling/debug (NN_GetWeights, NN_SetWeights)

The network is stateful (weights in RAM); persistence stays in the host (e.g., via NN_Get/SetWeights in MQL).

Features

  • Dense layers: W[out × in], bias b[out]
  • Activations: SIGMOID, RELU, TANH, LINEAR, SYM_SIG
  • Init: He for ReLU, Xavier-like for others
  • Gradient clipping (per neuron, ±5)
  • Double precision, x64 build (MSVC)
  • C ABI (no name mangling) → simple from MQL5
  • Batch wrappers for throughput
  • Weights I/O for reproducible runs

Deliberate limitations

  • Only vanilla SGD (no Adam/Nesterov yet)
  • No built-in serialization (persist in the host)
  • Thread safety: calls for one handle must be serialized by the host

Binary / Build info

  • Platform: Windows x64, MSVC (Visual Studio)
  • Exports: extern "C" __declspec(dllexport)
  • Exceptions: never cross the C boundary; API returns bool/int
  • Threading: instance table guarded by std::mutex; per-network ops are not re-entrant

Suggested VS settings

  • Configuration: Release | x64
  • C++: /std:c++17 (or newer), /O2, /EHsc
  • Runtime: /MD (shared CRT)

API (C interface)

All functions use the C ABI. Signatures:

// Create / free
int  NN_Create(void);
void NN_Free(int h);

// Topology (act: 0=SIGMOID, 1=RELU, 2=TANH, 3=LINEAR, 4=SYM_SIG)
bool NN_AddDense(int h, int inSz, int outSz, int act);

// Introspection
int  NN_InputSize(int h);
int  NN_OutputSize(int h);

// Inference
bool NN_Forward(int h, const double* in, int in_len,
                double* out, int out_len);
bool NN_ForwardBatch(int h, const double* in, int batch, int in_len,
                     double* out, int out_len);

// Training (SGD + MSE)
bool NN_TrainOne(int h, const double* in, int in_len,
                 const double* tgt, int tgt_len,
                 double lr, double* mse);
bool NN_TrainBatch(int h, const double* in, int batch, int in_len,
                   const double* tgt, int tgt_len,
                   double lr, double* mean_mse);

// Weights access
bool NN_GetWeights(int h, int i, double* W, int Wlen, double* b, int blen);
bool NN_SetWeights(int h, int i, const double* W, int Wlen, const double* b, int blen);

Activation codes (act)

CodeActivationNotes
0SIGMOID1/(1+e^{-x})
1RELUmax(0,x), He init
2TANH\tanh(x)
3LINEARIdentity
4SYM_SIG2·sigmoid(x)-1 ∈ (-1,1)

Lifecycle (recommended)

  1. h = NN_Create()
  2. Topology via NN_AddDense(...) in order
    • First layer sets the input size
    • Last layer sets the output size
    • Sizes must match: out(k-1) == in(k) → otherwise false
  3. Optionally check NN_InputSize(h), NN_OutputSize(h)
  4. Training: loop NN_TrainOne or NN_TrainBatch
  5. Inference: NN_Forward or NN_ForwardBatch
  6. NN_Free(h)

Semantics

  • Validation: functions return false for invalid handle, empty network, or size mismatch.
  • Memory safety: caller allocates and owns buffers; the DLL only reads/writes.
  • Numerics: double I/O; gradient clipping ±5 per neuron; He/Xavier-like init.
  • Instances: any number of independent networks (separate handles).
  • Re-entrancy: don’t call into the same handle concurrently.

Performance notes

  • Allocate in OnInit; train via timer; avoid per-tick create/destroy
  • Reuse MQL arrays to cut allocations
  • Balance lookback window vs. network width/depth

MQL5 compatibility

  • double ABI matches (8 bytes)
  • Arrays passed by reference; the DLL reads/writes via raw pointers
  • bool mapping is standard (0/1)

Troubleshooting

  • NN_AddDense returns false: size mismatch (e.g., 8→16 then 32→1). Fix to 8→16→1 or adjust layer sizes.
  • NN_Forward/Train* false: wrong lengths or invalid handle / empty network.
  • No learning: tune lr, normalize data, use LINEAR output for unbounded regression.
NNMQL5_Predictor — Examples (Dark)

NNMQL5_Predictor — Examples (Dark)

Code snippets based on indicator NNMQL5_Predictor.mq5 (MetaTrader 5 • NNMQL5.dll).

Hist +1 Hist +2 Hist +3 Hist +4 Hist +5 Future

License

“MIT-like spirit”: use freely, please keep attribution.


Author: Tomáš Bělák — Remind