forked from rosh/MQL5Book
322 lines
8.7 KiB
MQL5
322 lines
8.7 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| MatrixMachineLearning.mq5 |
| |
//| Copyright 2022, MetaQuotes Ltd. |
| |
//| https://www.mql5.com |
| |
//+------------------------------------------------------------------+
| |
#property script_show_inputs
| |
| |
input int TicksToLoad = 100;
| |
input int TicksToTest = 50;
| |
input int PredictorSize = 20;
| |
input int ForecastSize = 10;
| |
input bool DebugLog = false;
| |
| |
#include "..\..\Include\MqlError.mqh"
| |
| |
#define PRTX(A) ObjectPrint(#A, (A))
| |
#define LIMIT 100 // max steps to converge
| |
#define ACCURACY 0.00001 // neuron state change to converge
| |
| |
//+------------------------------------------------------------------+
| |
//| Helper printer returning object reference and checking errors |
| |
//+------------------------------------------------------------------+
| |
template<typename T>
| |
T ObjectPrint(const string s, const T &retval)
| |
{
| |
const int snapshot = _LastError; // required because _LastError is volatile
| |
const string err = E2S(snapshot) + "(" + (string)snapshot + ")";
| |
Print(s, "=", retval, " / ", (snapshot == 0 ? "ok" : err));
| |
ResetLastError(); // cleanup for next execution
| |
return retval;
| |
}
| |
| |
template<typename T>
| |
vector<T> Binary(const vector<T> &v)
| |
{
| |
vector<T> signs = vector<T>::Ones(v.Size());
| |
for(ulong i = 0; i < v.Size(); ++i)
| |
{
| |
if(v[i] < 0)
| |
{
| |
signs[i] = -1;
| |
}
| |
}
| |
return signs;
| |
}
| |
| |
template<typename T>
| |
void Binarize(vector<T> &v)
| |
{
| |
for(ulong i = 0; i < v.Size(); ++i)
| |
{
| |
if(v[i] < 0)
| |
{
| |
v[i] = -1;
| |
}
| |
else
| |
{
| |
v[i] = +1;
| |
}
| |
}
| |
}
| |
| |
template<typename T>
| |
matrix<T> TrainWeights(const vector<T> &data, const uint predictor, const uint responce,
| |
const uint start = 0, const uint _stop = 0, const uint step = 1)
| |
{
| |
const uint sample = predictor + responce;
| |
const uint stop = _stop <= start ? (uint)data.Size() : _stop;
| |
const uint n = (stop - sample + 1 - start) / step;
| |
matrix<T> A(n, predictor), B(n, responce);
| |
matrix<T> W = matrix<T>::Zeros(predictor, responce);
| |
| |
ulong k = 0;
| |
for(ulong i = start; i < stop - sample + 1; i += step, ++k)
| |
{
| |
for(ulong j = 0; j < predictor; ++j)
| |
{
| |
A[k][j] = data[start + i * step + j];
| |
}
| |
for(ulong j = 0; j < responce; ++j)
| |
{
| |
B[k][j] = data[start + i * step + j + predictor];
| |
}
| |
}
| |
| |
if(DebugLog)
| |
{
| |
PRTX(A);
| |
PRTX(B);
| |
}
| |
| |
for(ulong i = 0; i < k; ++i)
| |
{
| |
W += A.Row(i).Outer(B.Row(i));
| |
}
| |
| |
return W;
| |
}
| |
| |
template<typename T>
| |
vector<T> RunWeights(const matrix<T> &W,
| |
const vector<T> &data)
| |
{
| |
const uint predictor = (uint)W.Rows();
| |
const uint responce = (uint)W.Cols();
| |
| |
vector a = data;
| |
vector b = vector::Zeros(responce);
| |
| |
if(data.Size() != predictor)
| |
{
| |
Print("Predictor size mismatch: W=[%dx%d], data=%d",
| |
predictor, responce, data.Size());
| |
return b;
| |
}
| |
| |
vector x, y;
| |
uint j = 0;
| |
const uint limit = LIMIT;
| |
const matrix<T> w = W.Transpose();
| |
| |
for( ; j < limit; ++j)
| |
{
| |
x = a;
| |
y = b;
| |
a.MatMul(W).Activation(b, AF_TANH);
| |
b.MatMul(w).Activation(a, AF_TANH);
| |
if(!a.Compare(x, ACCURACY) && !b.Compare(y, ACCURACY)) break;
| |
}
| |
| |
if(DebugLog)
| |
{
| |
if(j < limit)
| |
{
| |
PrintFormat("Converged in %d cycles", j);
| |
}
| |
else
| |
{
| |
PrintFormat("Non-converged");
| |
}
| |
}
| |
| |
Binarize(a);
| |
Binarize(b);
| |
| |
if(DebugLog)
| |
{
| |
PRTX(a); // final state
| |
PRTX(b); // estimate
| |
}
| |
| |
return b;
| |
}
| |
| |
template<typename T>
| |
void CheckWeights(const matrix<T> &W,
| |
const vector<T> &data,
| |
const uint start = 0, const uint _stop = 0, const uint step = 1)
| |
{
| |
const uint predictor = (uint)W.Rows();
| |
const uint responce = (uint)W.Cols();
| |
const uint sample = predictor + responce;
| |
const uint stop = _stop <= start ? (uint)data.Size() : _stop;
| |
const uint n = (stop - sample + 1 - start) / step;
| |
matrix<T> A(n, predictor), B(n, responce);
| |
| |
ulong k = 0;
| |
for(ulong i = start; i < stop - sample + 1; i += step, ++k)
| |
{
| |
for(ulong j = 0; j < predictor; ++j)
| |
{
| |
A[k][j] = data[start + i * step + j];
| |
}
| |
for(ulong j = 0; j < responce; ++j)
| |
{
| |
B[k][j] = data[start + i * step + j + predictor];
| |
}
| |
}
| |
| |
const matrix<T> w = W.Transpose();
| |
| |
const uint limit = LIMIT;
| |
| |
int positive = 0;
| |
int negative = 0;
| |
int average = 0;
| |
| |
for(ulong i = 0; i < k; ++i)
| |
{
| |
vector a = A.Row(i);
| |
vector b = vector::Zeros(responce);
| |
| |
vector x, y;
| |
uint j = 0;
| |
| |
for( ; j < limit; ++j)
| |
{
| |
x = a;
| |
y = b;
| |
a.MatMul(W).Activation(b, AF_TANH);
| |
b.MatMul(w).Activation(a, AF_TANH);
| |
if(!a.Compare(x, ACCURACY) && !b.Compare(y, ACCURACY)) break;
| |
}
| |
| |
if(DebugLog)
| |
{
| |
if(j < limit)
| |
{
| |
PrintFormat("%d: Converged in %d cycles", i, j);
| |
}
| |
else
| |
{
| |
PrintFormat("%d: Non-converged", i);
| |
}
| |
}
| |
| |
Binarize(a);
| |
Binarize(b);
| |
const int match = (int)(b.Dot(B.Row(i)));
| |
if(match > 0) positive++;
| |
else if(match < 0) negative++;
| |
| |
if(DebugLog)
| |
{
| |
PRTX(a); // final state
| |
PRTX(b); // estimate
| |
PRTX(B.Row(i)); // target
| |
PRTX(match);
| |
}
| |
| |
average += match; // 0's match means 50/50 prediction (naive guessing)
| |
}
| |
float skew = (float)average / k;
| |
| |
PrintFormat("Count=%d Positive=%d Negative=%d Accuracy=%.2f%%",
| |
k, positive, negative, ((skew + responce) / 2 / responce) * 100);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Script program start function |
| |
//+------------------------------------------------------------------+
| |
void OnStart()
| |
{
| |
// get latest ticks
| |
MqlTick ticks[];
| |
const int n = CopyTicks(_Symbol, ticks, COPY_TICKS_ALL, 0, TicksToLoad);
| |
if(n != TicksToLoad)
| |
{
| |
PrintFormat("Insufficient ticks: %d, error: %d", n, _LastError);
| |
return;
| |
}
| |
| |
// could use new feature - 'vector|matrix::CopyTicks' method
| |
// vector ticks;
| |
// ticks.CopyTicks(_Symbol, COPY_TICKS_ALL | COPY_TICKS_ASK, 0, TicksToLoad);
| |
// if(ticks.Size() != TicksToLoad) return; // error
| |
| |
// extract ticks for training on backtest
| |
vector ask1(n - TicksToTest);
| |
for(int i = 0; i < n - TicksToTest; ++i)
| |
{
| |
ask1[i] = ticks[i].ask;
| |
}
| |
| |
// extract more ticks for testing on forward
| |
vector ask2(TicksToTest);
| |
for(int i = 0; i < TicksToTest; ++i)
| |
{
| |
ask2[i] = ticks[i + TicksToLoad - TicksToTest].ask;
| |
}
| |
| |
// calculate price changes
| |
vector differentiator = {+1, -1};
| |
vector deltas = ask1.Convolve(differentiator, VECTOR_CONVOLVE_VALID);
| |
| |
vector inputs = Binary(deltas);
| |
if(DebugLog)
| |
{
| |
PRTX(deltas);
| |
PRTX(inputs);
| |
}
| |
| |
matrix W = TrainWeights(inputs, PredictorSize, ForecastSize);
| |
| |
Print("Check training on backtest: ");
| |
CheckWeights(W, inputs);
| |
| |
vector test = Binary(ask2.Convolve(differentiator, VECTOR_CONVOLVE_VALID));
| |
Print("Check training on forwardtest: ");
| |
CheckWeights(W, test);
| |
| |
Sleep(1000); // wait a bit, so some ticks are probably added
| |
| |
// could use new feature - 'vector|matrix::CopyTicks' method
| |
// ticks.CopyTicks(_Symbol, COPY_TICKS_ALL | COPY_TICKS_ASK, 0, PredictorSize + 1);
| |
// if(ticks.Size() == PredictorSize + 1) ...
| |
const int m = CopyTicks(_Symbol, ticks, COPY_TICKS_ALL, 0, PredictorSize + 1);
| |
if(m == PredictorSize + 1)
| |
{
| |
vector ask3(PredictorSize + 1);
| |
for(int i = 0; i < PredictorSize + 1; ++i)
| |
{
| |
ask3[i] = ticks[i].ask;
| |
}
| |
vector online = Binary(ask3.Convolve(differentiator, VECTOR_CONVOLVE_VALID));
| |
Print("Online: ", online);
| |
vector forecast = RunWeights(W, online);
| |
Print("Forecast: ", forecast);
| |
}
| |
}
| |
//+------------------------------------------------------------------+
| |
/*
| |
| |
Check training on backtest:
| |
Count=20 Positive=20 Negative=0 Accuracy=85.50%
| |
Check training on forwardtest:
| |
Count=20 Positive=12 Negative=2 Accuracy=58.50%
| |
Online: [1,1,1,1,-1,-1,-1,1,-1,1,1,-1,1,1,-1,-1,1,1,-1,-1]
| |
Forecast: [-1,1,-1,1,-1,-1,1,1,-1,1]
| |
| |
*/
| |
//+------------------------------------------------------------------+
|