Neural Networks Without Magic: Occam’s Razor and a Programmer’s View
Artificial intelligence doesn’t have to be esoteric. High school math, a few derivatives, and clean code in C++/MQL5 are enough. No magic—just numbers in matrices, functions, and discipline.

A Problem Without Fog: What We Really Need
Texts about neural networks often contain a lot of “smoke and mirrors.” I used Occam’s razor and kept only what is necessary—the mathematical minimum everyone remembers from high school:
- multiplication and addition of numbers,
- simple nonlinear functions (tanh, sigmoid, ReLU),
- basic work with vectors and matrices,
- and derivatives for learning (backpropagation).
That’s it. Everything else is just comfort, not necessity.
Derivatives: The Only “Optional Mandatory” Chapter
Without derivatives, we only get the result of the forward pass. But learning requires knowing how to adjust each weight. This is where the chain rule comes in: compute the error, send it back through the layers, and adjust weights and biases according to the derivatives of activations and linear parts.
- Error: difference between prediction and reality (MSE).
- Gradient: derivative of the composite function (chain rule).
- Update: a small step in the direction of error reduction (SGD).
If you can handle the derivative of sigmoid, tanh, and “ReLU derivative,” you’ve covered 99% of what’s needed.
C++ and MQL5: Math Translated into Practice
In C++ I have a lightweight DLL: dense layers, activations, forward pass, backpropagation, single-sample and mini-batch training. Everything runs through a simple C API (handle, no global singletons). In MQL5, the EA takes data directly from the chart, normalizes it, feeds it into the network, and draws 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 tooling (
NN_GetWeights
,NN_SetWeights
).
Result: AI stops looking magical. What remains is programming craft with understandable behavior.
Occam’s Razor in Bullet Points
- Forward pass: matrix–vector multiplication.
- Activation: tanh/sigmoid/ReLU as ordinary functions.
- Loss (MSE): mean squared error.
- Backpropagation: pure chain rule.
Complexity arises from stacking simple things, not from mystery.
What This Path Gave Me
I see the network as a system of calculation, not a prophecy. I understand both its limits and possibilities. And I have a tool I can expand: save/load weights (via app code), add Adam, more outputs, connect it to trading logic.
High school math, patience, and the will to remove ballast are enough.
Conclusion
Neural networks are not a black box. They are numbers in matrices and a few derivatives—a mathematical mechanism translated into the languages I like: C++ and MQL5.
Download (EA / sources)
NNMQL5 Documentation:
A lightweight DLL exposing a C API for a simple MLP (stack of dense layers) with:
- forward inference (
NN_Forward
,NN_ForwardBatch
) - training: single-sample and mini-batch (
NN_TrainOne
,NN_TrainBatch
) - multi-network instance management via integer handles
- weights access for tooling/debug (
NN_GetWeights
,NN_SetWeights
)
The network is stateful (weights in RAM); persistence is handled by the host (e.g., save/load via NN_Get/SetWeights
from MQL).
Features
- Dense layers:
W[out x in]
, biasb[out]
- Activations:
SIGMOID
,RELU
,TANH
,LINEAR
,SYM_SIG
(symmetric sigmoid) - Weight init: He for ReLU, Xavier-like for others
- Gradient clipping (per-neuron, ±5)
- Double precision, x64 build (MSVC)
- C ABI (no name mangling) → easy to call from MQL5
- Batch wrappers for throughput
- Weights read/write for reproducible experiments
Limitations (by design)
- No built-in optimizer beyond SGD (Adam/Nesterov not included yet)
- No built-in serialization API (persist via
NN_Get/SetWeights
in host code) - Thread safety: per-handle calls 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 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
)
Code | Activation | Notes |
---|---|---|
0 | SIGMOID | 1/(1+e^{-x}) |
1 | RELU | max(0,x) , He init |
2 | TANH | \tanh(x) |
3 | LINEAR | Identity |
4 | SYM_SIG | 2·sigmoid(x)-1 ∈ (-1,1) |
Lifecycle (recommended)
h = NN_Create()
- Topology via
NN_AddDense(...)
in order- First layer sets input size
- Last layer sets output size
- Sizes must match:
out(k-1) == in(k)
→ returnsfalse
otherwise
- Optionally check
NN_InputSize(h)
,NN_OutputSize(h)
- Training: loop
NN_TrainOne
orNN_TrainBatch
- Inference:
NN_Forward
orNN_ForwardBatch
NN_Free(h)
Semantics
- Validation: functions return
false
for invalid handle, empty network, or size mismatch. - Memory safety: caller allocates and owns buffers; 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.
MQL5 usage
Import block
#import "NNMQL5.dll"
int NN_Create();
void NN_Free(int h);
bool NN_AddDense(int h,int inSz,int outSz,int act);
int NN_InputSize(int h);
int NN_OutputSize(int h);
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);
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);
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);
#import
Note: Arrays are passed as
const double &arr[]
in MQL5. Place the DLL in%APPDATA%\MetaQuotes\Terminal\<id>\MQL5\Libraries\
and allow DLL imports in MT5 settings.
Minimal network + inference (e.g., 4→8→1)
int h = NN_Create();
if(h <= 0){ Print("Create failed"); return; }
bool ok = true;
ok &= NN_AddDense(h, 4, 8, 1); // RELU
ok &= NN_AddDense(h, 8, 1, 3); // LINEAR
if(!ok){ Print("Topology failed"); NN_Free(h); return; }
double x[4] = {1,2,3,4};
double y[1];
if(!NN_Forward(h, x, 4, y, 1)) Print("Forward failed");
Print("y=", y[0]);
NN_Free(h);
One SGD step
double x[4] = {/* features */};
double t[1] = {/* target */};
double mse=0.0, lr=0.01;
bool ok = NN_TrainOne(h, x, 4, t, 1, lr, mse);
if(!ok) Print("TrainOne failed");
else Print("MSE=", DoubleToString(mse,6));
Mini-batch training
const int BATCH=32, IN=240, OUT=15;
double X[BATCH*IN], T[BATCH*OUT], mean_mse;
if(!NN_TrainBatch(h, X, BATCH, IN, T, OUT, 0.001, mean_mse))
Print("TrainBatch failed");
Quick reference (return values)
Function | Success (true/>0) | False/0 means |
---|---|---|
NN_Create | Positive handle | Allocation failed (rare) |
NN_Free | — | — (idempotent) |
NN_AddDense | Layer added | Invalid handle / size mismatch |
NN_InputSize | Declared input size | 0 (invalid handle / empty network) |
NN_OutputSize | Declared output size | 0 (invalid handle / empty network) |
NN_Forward | out[] filled | Invalid handle/sizes/empty network |
NN_TrainOne | Update applied; *mse set | Invalid handle/sizes/empty network |
NN_ForwardBatch | All rows processed | Invalid handle/sizes/empty network |
NN_TrainBatch | Mean MSE set | Invalid handle/sizes/empty network |
Implementation notes (for audit)
- Forward: per layer
z = W·x + b
; cacheslast_in
,last_z
,last_out
- Backward:
dL/dz = dL/dy ⊙ f'(z)
(ReLU usesx>0
); clip ±5; updateb -= lr·dL/dz
,W -= lr·(dL/dz)·last_in^T
- Loss:
MSE = mean((y - t)^2)
, gradientdL/dy = 2(y - t)/n
- Init:
w = U[-1,1]·scale
,scale = sqrt(2/in)
for ReLU, elsesqrt(1/in)
Performance notes
- Allocate in
OnInit
; train via timer; avoid per-tick create/destroy - Reuse MQL arrays to reduce allocations
- Balance lookback vs. network width/depth
MQL5 compatibility
double
ABI matches (8B)- Arrays passed by reference; 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)
Code snippets based on indicator NNMQL5_Predictor.mq5
(MetaTrader 5 • NNMQL5.dll).
License note
“MIT-like spirit”: use freely, please keep attribution.

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