//+------------------------------------------------------------------+ //| 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 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 vector Binary(const vector &v) { vector signs = vector::Ones(v.Size()); for(ulong i = 0; i < v.Size(); ++i) { if(v[i] < 0) { signs[i] = -1; } } return signs; } template void Binarize(vector &v) { for(ulong i = 0; i < v.Size(); ++i) { if(v[i] < 0) { v[i] = -1; } else { v[i] = +1; } } } template matrix TrainWeights(const vector &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 A(n, predictor), B(n, responce); matrix W = matrix::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 vector RunWeights(const matrix &W, const vector &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 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 void CheckWeights(const matrix &W, const vector &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 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 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] */ //+------------------------------------------------------------------+