Matrizes e vetores em MQL5

MetaQuotes | 8 março, 2022

Para manusear dados ordenados e homogêneos, é comum o uso de matrizes que permitem que cada elemento seja acessado por um índice. As matrizes são amplamente utilizadas para resolver muitos problemas de álgebra linear, modelagem matemática, aprendizagem de máquinas, etc. A solução para estes problemas é, em termos gerais, baseada em operações matemáticas que utilizam matrizes e vetores, que permitem que conversões muito complexas sejam escritas de forma compacta como uma fórmula simples. Programar estas operações não só requer um bom nível de matemática, mas também saber escrever laços aninhados complexos. A depuração e a detecção de bugs neste tipo de programas pode ser muito cansativa. 

Os tipos de dados especiais matrix e vector permite escrever um código próximo a uma notação matemática e evita a necessidade de criar laços aninhados. O programador não precisa mais se lembrar de indexar corretamente as matrizes que estão envolvidas no cálculo. Neste artigo mostramos como criar, iniciar e aplicar os objetos matrix e vector em MQL5.


O tipo vector

vector é uma matriz unidimensional do tipo double. As operações de adição e multiplicação são definidas sobre vetores, e o conceito de "Norma" é introduzido para obter o comprimento ou módulo de um vetor. Na programação, os vetores são normalmente representados por matrizes de elementos homogêneos sobre os quais as operações vetoriais usuais podem não ser especificadas, ou seja, as matrizes não podem ser adicionadas umas às outras, multiplicadas umas pelas outras, e não existe o conceito de norma.

Em matemática, os vetores podem ser representados como uma fila vetorial, isto é, como uma matriz de uma fila e n colunas, ou como uma coluna vetorial como uma matriz de uma coluna e n filas. Como em MQL5 o tipo "vector" não é dividido em linhas e colunas vetoriais, o programador deve entender ele mesmo qual tipo de vetor é usado ao realizar uma determinada operação.

É possível criar e iniciar um vetor usando métodos embutidos.

Métodos
Análogo NumPy
Descrição
void vector.Init( ulong size);   Cria um vetor de comprimento especificado, cujos valores são indefinidos
static vector vector::Ones(ulong size);
 ones
Cria um vetor de comprimento especificado e o preenche com uns
static vector vector::Zeros(ulong size);
 zeros
Cria um vetor de comprimento especificado e o preenche com zeros
static vector vector::Full(ulong size,double value);
 full
Cria um vetor de comprimento especificado e o preenche com os valores definidos
operador =

Devolve a cópia do vetor
void vector.Resize(const vector v);
  Muda o tamanho do vetor adicionando novos valores a partir do final 


Exemplos de criação vetorial:

void OnStart()
 {
//--- vector initialization examples
  vector v;
  v.Init(7);
  Print("v = ", v); 

  vector v1=vector::Ones(5);
  Print("v1 = ", v1);

  vector v2=vector::Zeros(3);
  Print("v2 = ", v2);

  vector v3=vector::Full(6, 2.5);
  Print("v3 = ", v3);

  vector v4{1, 2, 3};
  Print("v4 = ", v4);  
  v4.Resize(5);
  Print("after Resize(5) v4 = ", v4);  
  
  vector v5=v4;
  Print("v5 = ", v5);  
  v4.Fill(7);
  Print("v4 = ", v4, "   v5 =",v5);  
   
 }
 
 
/*
Execution result

v = [4,5,6,8,10,12,12]
v1 = [1,1,1,1,1]
v2 = [0,0,0]
v3 = [2.5,2.5,2.5,2.5,2.5,2.5]
v4 = [1,2,3]
after Resize(5) v4 = [1,2,3,7,7]
v5 = [1,2,3,7,7]
v4 = [7,7,7,7,7]   v5 =[1,2,3,7,7]

*/ 

O método Init pode ser usado não apenas para alocar memória para um vetor, mas também para inicializar elementos vetoriais com valores usando uma função. Neste caso, o primeiro parâmetro em Init é o tamanho do vetor e o segundo é o nome da função. Se a função por sua vez tiver parâmetros, estes são indicados imediatamente após o nome da função, separados por vírgulas.

A função em si deve conter uma referência ao vetor que lhe é passado como primeiro parâmetro, mas o vetor não precisa ser passado ao chamar Init. Vamos mostrar isto com um exemplo da função Arange que simula numpy.arange.

void OnStart()
  {
//---
   vector v;
   v.Init(7,Arange,10,0,0.5); // 3 parameters are passed with Arange call
   Print("v = ", v);
   Print("v.size = ",v.Size());
  }
//+------------------------------------------------------------------+
//|  Values are generated within the half-open interval [start, stop)|
//+------------------------------------------------------------------+
void Arange(vector& v, double stop, double start = 0, double step = 1) // the function has 4 parameters
  {
   if(start >= stop)
     {
      PrintFormat("%s wrong parameters! start=%G  stop=%G", __FILE__,start, stop);
      return;
     }
//---
   int size = (int)((stop - start) / step);
   v.Resize(size);
   double value = start;
   for(ulong i = 0; i < v.Size(); i++)
     {
      v[i] = value;
      value += step;
     }
  }
  
/*
Execution result

v = [0,0.5,1,1.5,2,2.5,3,3.5,4,4.5,5,5.5,6,6.5,7,7.5,8,8.5,9,9.5]
v.size = 20

*/

A função Arange tem dois parâmetros opcionais - "start" e "step". Por isso, vamos permitir esta forma de chamar Init(7,Arange,10) com o resultado correspondente:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   vector v;
   v.Init(7,Arange,10);
   Print("v = ", v);
   Print("v.size = ",v.Size());
  }
...

/*

v = [0,1,2,3,4,5,6,7,8,9]
v.size = 10

*/


Operações com vetores

As operações usuais de adição, subtração, multiplicação e divisão podem ser realizadas em vetores usando um escalar.

//+------------------------------------------------------------------+
//|                                              vector2_article.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
 {
//---
  vector v= {1, 2, 3, 4, 5};
  Print("Examples without saving vector changes");
  Print("v = ", v);
  Print("v+5 = ", v+5);
  Print("v-Pi= ", v-M_PI);
  Print("v*2.0= ", v*2);
  Print("v/3.0= ", v/3.0);

  Print("Save all vector changes");
  Print("v = ", v);
  Print("v+5 = ", v=v+5);
  Print("v-Pi= ", v=v-M_PI);
  Print("v*2.0= ", v= v*2);
  Print("v/3.0= ", v= v/3.0);
 }
/*
Execution result
   
Examples without saving vector changes
v = [1,2,3,4,5]
v+5 = [6,7,8,9,10]
v-Pi= [-2.141592653589793,-1.141592653589793,-0.1415926535897931,0.8584073464102069,1.858407346410207]
v*2.0= [2,4,6,8,10]
v/3.0= [0.3333333333333333,0.6666666666666666,1,1.333333333333333,1.666666666666667]
Save all vector changes
v = [1,2,3,4,5]
v+5 = [6,7,8,9,10]
v-Pi= [2.858407346410207,3.858407346410207,4.858407346410207,5.858407346410207,6.858407346410207]
v*2.0= [5.716814692820414,7.716814692820414,9.716814692820414,11.71681469282041,13.71681469282041]
v/3.0= [1.905604897606805,2.572271564273471,3.238938230940138,3.905604897606805,4.572271564273471]

*/
//+------------------------------------------------------------------+

São definidas as operações de adição, subtração, multiplicação e divisão de dois vetores do mesmo tamanho, elemento por elemento.

void OnStart()
  {
//---
   vector a = {1, 2, 3};
   vector b = {2, 4, 6};
   Print("a + b = ", a + b);
   Print("a - b = ", a - b);
   Print("a * b = ", a * b);
   Print("b / a = ", b / a);
  }

/*
Execution result

a + b = [3,6,9]
a - b = [-1,-2,-3]
a * b = [2,8,18]
b / a = [2,2,2]

*/

Também são definidas quatro operações de produtos vetoriais.

void OnStart()
 {
//---
  vector a={1, 2, 3};
  vector b={4, 5, 6};
  Print("a = ", a);
  Print("b = ", b);
  Print("1) a.Dot(b) = ", a.Dot(b));
  Print("2) a.MatMul(b) = ", a.MatMul(b));
  Print("3) a.Kron(b) = ", a.Kron(b));
  Print("4) a.Outer(b) = \n", a.Outer(b));
 }
/*
Execution result

a = [1,2,3]
b = [4,5,6]
1) a.Dot(b) = 32.0
2) a.MatMul(b) = 32.0
3) a.Kron(b) = [[4,5,6,8,10,12,12,15,18]]
4) a.Outer(b) = 
[[4,5,6]
 [8,10,12]
 [12,15,18]]

*/

Como se pode ver no exemplo, o método Outer() retorna uma matriz na qual o número de filas e colunas corresponde ao tamanho dos vetores a serem multiplicados. Os métodos Dot() e MatMul() funcionam da mesma maneira. Isto supõe que na operação Outer(), o vetor à esquerda é vertical e o vetor à direita é horizontal. Para os métodos Dot() e MatMul(), a ordem vetorial é invertida - o vetor horizontal está à esquerda e o vetor vertical está à direita.


Norma do vetor

Sobre vetores e matrizes, é definido o conceito de normalidade, que representa o conceito de comprimento ou valor absoluto de um vetor. Há um total de três alternativas para o cálculo de normas vetoriais, que estão listadas em ENUM_VECTOR_NORM.
void OnStart()
 {
//---
  struct str_vector_norm
   {
    ENUM_VECTOR_NORM  norm;
    int               value;
   };
  str_vector_norm vector_norm[]=
   {
     {VECTOR_NORM_INF,       0},
     {VECTOR_NORM_MINUS_INF, 0},
     {VECTOR_NORM_P,         0},
     {VECTOR_NORM_P,         1},
     {VECTOR_NORM_P,         2},
     {VECTOR_NORM_P,         3},
     {VECTOR_NORM_P,         4},
     {VECTOR_NORM_P,         5},
     {VECTOR_NORM_P,         6},
     {VECTOR_NORM_P,         7},
     {VECTOR_NORM_P,        -1},
     {VECTOR_NORM_P,        -2},
     {VECTOR_NORM_P,        -3},
     {VECTOR_NORM_P,        -4},
     {VECTOR_NORM_P,        -5},
     {VECTOR_NORM_P,        -6},
     {VECTOR_NORM_P,        -7}
   };
  vector v{1, 2, 3, 4, 5, 6, 7};
  double norm;
  Print("v = ", v);
//---
  for(int i=0; i<ArraySize(vector_norm); i++)
   {
    switch(vector_norm[i].norm)
     {
      case VECTOR_NORM_INF :
        norm=v.Norm(VECTOR_NORM_INF);
        Print("v.Norm(VECTOR_NORM_INF) = ", norm);
        break;
      case VECTOR_NORM_MINUS_INF :
        norm=v.Norm(VECTOR_NORM_MINUS_INF);
        Print("v.Norm(VECTOR_NORM_MINUS_INF) = ", norm);
        break;
      case VECTOR_NORM_P :
        norm=v.Norm(VECTOR_NORM_P, vector_norm[i].value);
        PrintFormat("v.Norm(VECTOR_NORM_P,%d) = %G", vector_norm[i].value, norm);
     }
   }
 }
/*

v = [1,2,3,4,5,6,7]
v.Norm(VECTOR_NORM_INF) = 7.0
v.Norm(VECTOR_NORM_MINUS_INF) = 1.0
v.Norm(VECTOR_NORM_P,0) = 7
v.Norm(VECTOR_NORM_P,1) = 28
v.Norm(VECTOR_NORM_P,2) = 11.8322
v.Norm(VECTOR_NORM_P,3) = 9.22087
v.Norm(VECTOR_NORM_P,4) = 8.2693
v.Norm(VECTOR_NORM_P,5) = 7.80735
v.Norm(VECTOR_NORM_P,6) = 7.5473
v.Norm(VECTOR_NORM_P,7) = 7.38704
v.Norm(VECTOR_NORM_P,-1) = 0.385675
v.Norm(VECTOR_NORM_P,-2) = 0.813305
v.Norm(VECTOR_NORM_P,-3) = 0.942818
v.Norm(VECTOR_NORM_P,-4) = 0.980594
v.Norm(VECTOR_NORM_P,-5) = 0.992789
v.Norm(VECTOR_NORM_P,-6) = 0.99714
v.Norm(VECTOR_NORM_P,-7) = 0.998813

*/

Você pode usar a norma para medir a distância entre dois vetores:

void OnStart()
 {
//---
   vector a{1,2,3};
   vector b{2,3,4};
   double distance=(b-a).Norm(VECTOR_NORM_P,2);
   Print("a = ",a);
   Print("b = ",b);
   Print("|a-b| = ",distance);   
 }
/*
Execution result

a = [1,2,3]
b = [2,3,4]
|a-b| = 1.7320508075688772

*/


Tipo matrix

Um vetor é um caso especial de uma matriz, que na verdade é uma matriz bidimensional do tipo double. Assim, podemos dizer que uma matriz é um conjunto de vetores do mesmo tamanho. O número de filas na matriz é igual ao número de vetores e o número de colunas é igual ao comprimento dos vetores. 

As operações de adição e multiplicação também são definidas sobre matrizes. Linguagens de programação comuns usam arrays para representar matrizes. Mas as matrizes regulares não podem ser adicionadas umas às outras, não podem ser multiplicadas umas pelas outras e não há definição de uma norma para elas. Em matemática, são considerados muitos tipos e tipos diferentes de matrizes. Por exemplo, unidades, simétricas, obliquamente simétricas, triangulares superiores (triangulares inferiores) e outras matrizes.

Uma matriz pode ser criada e iniciada usando métodos incorporados similares aos métodos vetoriais.

Método

Contraparte em NumPy

Descrição

void static matrix.Eye(const int rows, const int cols, const int ndiag=0)

eye

Cria uma matriz com uns na diagonal especificada e zeros em outros lugares

void matrix.Identity()

identity

Preenche a matriz com uns na diagonal principal e zeros em outros lugares

void static matrix.Ones(const int rows, const int cols)

ones

Cria uma nova matriz por número de filas e colunas, preenchida com unidades

void static matrix.Zeros(const int rows, const int cols)

zeros

Cria uma nova matriz pelo número de filas e colunas preenchida com zeros

void static matrix.Tri(const int rows, const int cols, const int ndiag=0)
tri  Cria uma matriz com uns na diagonal indicada e abaixo, e zeros em outros lugares 
void matrix.Diag(const vector v, const int ndiag=0)  diag  Extrai uma diagonal ou cria uma matriz diagonal 

void matrix.Full(const int rows, const int cols, const scalar value)

full

Cria uma nova matriz pelo número de filas e colunas, preenchidas com um valor escalar

void matrix.Fill(const scalar value)     Preenche a matriz com o valor especificado


Exemplos de criação e de preenchimento de matrizes:

void OnStart()
 {
//---
  matrix m{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
  Print("m = \n", m);
  matrix ones=matrix::Ones(4, 4);
  Print("ones = \n", ones);
  matrix zeros=matrix::Zeros(4, 4);
  Print("zeros = \n", zeros);
  matrix eye=matrix::Eye(4, 4);
  Print("eye = \n", eye);

  matrix identity(4, 5);
  Print("matrix_identity\n", identity);
  identity.Identity();
  Print("matrix_identity\n", identity);

  matrix tri=matrix::Tri(3, 4);
  Print("tri = \n", tri);
  Print("tri.Transpose() = \n", tri.Transpose()); // transpose the matrix

  matrix diag(5, 5);
  Print("diag = \n", diag);
  vector d{1, 2, 3, 4, 5};
  diag.Diag(d);
  Print("diag = \n", diag); // insert values from the vector into the matrix diagonal

  matrix fill(5, 5);
  fill.Fill(10);
  Print("fill = \n", fill);

  matrix full =matrix::Full(5, 5, 100);
  Print("full = \n", full);


  matrix init(5, 7);
  Print("init = \n", init);
  m.Init(4, 6);
  Print("init = \n", init);
  
  matrix resize=matrix::Full(2, 2, 5);  
  resize.Resize(5,5);
  Print("resize = \n", resize);  
 }
/*
Execution result

m =
[[1,2,3]
[4,5,6]
[7,8,9]]
ones =
[[1,1,1,1]
[1,1,1,1]
[1,1,1,1]
[1,1,1,1]]
zeros =
[[0,0,0,0]
[0,0,0,0]
[0,0,0,0]
[0,0,0,0]]
eye =
[[1,0,0,0]
[0,1,0,0]
[0,0,1,0]
[0,0,0,1]]
matrix_identity
[[1,0,0,0,0]
[0,1,0,0,0]
[0,0,1,0,0]
[0,0,0,1,0]]
matrix_identity
[[1,0,0,0,0]
[0,1,0,0,0]
[0,0,1,0,0]
[0,0,0,1,0]]
tri =
[[1,0,0,0]
[1,1,0,0]
[1,1,1,0]]
tri.Transpose() =
[[1,1,1]
[0,1,1]
[0,0,1]
[0,0,0]]
diag =
[[0,0,0,0,0]
[0,0,0,0,0]
[0,0,0,0,0]
[0,0,0,0,0]
[0,0,0,0,0]]
diag =
[[1,0,0,0,0]
[0,2,0,0,0]
[0,0,3,0,0]
[0,0,0,4,0]
[0,0,0,0,5]]
fill =
[[10,10,10,10,10]
[10,10,10,10,10]
[10,10,10,10,10]
[10,10,10,10,10]
[10,10,10,10,10]]
full =
[[100,100,100,100,100]
[100,100,100,100,100]
[100,100,100,100,100]
[100,100,100,100,100]
[100,100,100,100,100]]
resize = 
[[5,5,0,0,0]
 [5,5,0,0,0]
 [0,0,0,0,0]
 [0,0,0,0,0]
 [0,0,0,0,0]]

*/

O exemplo a seguir mostra como as funções próprias podem ser utilizadas no preenchimento de matrizes:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
 {
//---
  matrix random(4, 5, MatrixRandom);
  Print("random = \n",random);
  
  matrix init(3, 6, MatrixSetValues);  
  Print("init = \n", init);
  
 }
//+------------------------------------------------------------------+
//| Fills the matrix with random values                              |
//+------------------------------------------------------------------+
void MatrixRandom(matrix& m)
 {
  for(ulong r=0; r<m.Rows(); r++)
   {
    for(ulong c=0; c<m.Cols(); c++)
     {
      m[r][c]=double(MathRand())/32767.;
     }
   }
 }
//+------------------------------------------------------------------+
//| Fills the matrix with powers of a number                         |
//+------------------------------------------------------------------+
void MatrixSetValues(matrix& m, double initial=1)
 {
  double value=initial;
  for(ulong r=0; r<m.Rows(); r++)
   {
    for(ulong c=0; c<m.Cols(); c++)
     {
      m[r][c]=value;
      value*=2;
     }
   }
 } 
 
/*
Execution result

random = 
[[0.4200262459181494,0.5014496292001098,0.7520371105075229,0.652058473464156,0.08783227027191992]
 [0.5991088595233008,0.4311960203863643,0.8718832972197638,0.1350138859218116,0.901882992034669]
 [0.4964445936460463,0.8354747154148991,0.5258339182714317,0.6055482650227363,0.5952940458388012]
 [0.3959166234321116,0.8146916104617451,0.2053590502639851,0.2657551805169835,0.3672292245246742]]
init = 
[[1,2,4,8,16,32]
 [64,128,256,512,1024,2048]
 [4096,8192,16384,32768,65536,131072]]

*/ 

Além disso, observe que há duas maneiras de criar uma matriz sem inicializar os valores. 

//--- create a matrix of a given 'rows x cols' size
  matrix m(3, 3);

// ------ equivalent
  matrix m;
  m.Resize(3, 3);


Norma de matriz

Há um total de nove alternativas para o cálculo da norma da matriz. Estão listadas em ENUM_MATRIX_NORM.

void OnStart()
  {
//---
   ENUM_MATRIX_NORM matrix_norm[]= {MATRIX_NORM_FROBENIUS,
                                    MATRIX_NORM_SPECTRAL,
                                    MATRIX_NORM_NUCLEAR,
                                    MATRIX_NORM_INF,
                                    MATRIX_NORM_MINUS_INF,
                                    MATRIX_NORM_P1,
                                    MATRIX_NORM_MINUS_P1,
                                    MATRIX_NORM_P2,
                                    MATRIX_NORM_MINUS_P2
                                   };
   matrix m{{1,2,3},{4,5,6},{7,8,9}};
   Print("matrix m:\n",m);
//--- compute the norm using all ways
   double norm;
   for(int i=0; i<ArraySize(matrix_norm); i++)
     {
      norm=m.Norm(matrix_norm[i]);
      PrintFormat("%d. Norm(%s) = %.6f",i+1, EnumToString(matrix_norm[i]),norm);
     }
//---
   return;
  }

/*
Execution result

matrix m:
[[1,2,3]
[4,5,6]
[7,8,9]]
1. Norm(MATRIX_NORM_FROBENIUS) = 16.881943
2. Norm(MATRIX_NORM_SPECTRAL) = 14.790157
3. Norm(MATRIX_NORM_NUCLEAR) = 17.916473
4. Norm(MATRIX_NORM_INF) = 24.000000
5. Norm(MATRIX_NORM_MINUS_INF) = 6.000000
6. Norm(MATRIX_NORM_P1) = 18.000000
7. Norm(MATRIX_NORM_MINUS_P1) = 12.000000
8. Norm(MATRIX_NORM_P2) = 16.848103
9. Norm(MATRIX_NORM_MINUS_P2) = 0.000000

*/


Operações com matrizes e vetores

As matrizes contêm métodos especiais para a solução de problemas matemáticos:

Decomposição de matriz:

Método

Contraparte em NumPy

Descrição
bool matrix.Cholesky(matrix& L) cholesky Calcula a decomposição de Cholecky.
bool matrix.QR(matrix& Q, matrix& R) qr

Calcula a fatoração qr

bool matrix.SVD(matrix& U, matrix& V, vector& singular_values)

svd

Calcula a decomposição SVD

bool matrix.Eig(matrix& eigen_vectors, vector& eigen_values)

eig

Calcula os valores próprios e os vetores próprios direitos de uma matriz quadrada

bool matrix.EigVals(vector& eigen_values)

eigvals

Calcula os valores próprios de uma matriz comum

bool matrix.LU(matrix& L, matrix& U)

 

Executa a decomposição LU de uma matriz como o produto de uma matriz triangular inferior e uma matriz triangular superior

bool matrix.LUP(matrix& L, matrix& U, matrix& P)

 

Executa a decomposição LUP de rotação parcial, que é fatoração LU com permutações de linha: PA = LU


Produto de matrizes e vetores

Para efetuar o produto matricial de matrizes e vetores, é definido o método MatMul(). É frequentemente utilizado na solução de muitos problemas matemáticos. Ao multiplicar uma matriz e um vetor, apenas são permitidas duas alternativas:

Se o comprimento do vetor não for igual ao número de colunas da matriz, será causado um erro crítico de execução.

As matrizes da seguinte forma podem ser multiplicadas umas com as outras A[M,N] * B[N,K] = С[M,K], ou seja, o número de colunas na matriz da esquerda deve ser igual ao número de filas na matriz da direita. Se as dimensões não forem concertadas, o resultado será uma matriz vazia. Vamos mostrar todas as variações do produto da matriz através de exemplos.

void OnStart()
  {
//--- initialize matrices
   matrix m35, m52;
   m35.Init(3,5,Arange);
   m52.Init(5,2,Arange);
//---
   Print("1. Product of horizontal vector v[3] and matrix m[3,5]");
   vector v3 = {1,2,3};
   Print("On the left v3 = ",v3);
   Print("On the right m35 = \n",m35);
   Print("v3.MatMul(m35) = horizontal vector v[5] \n",v3.MatMul(m35));
//--- show that this is really a horizontal vector
   Print("\n2. Product of matrix m[1,3] and matrix m[3,5]");
   matrix m13;
   m13.Init(1,3,Arange,1);
   Print("On the left m13 = \n",m13);
   Print("On the right m35 = \n",m35);
   Print("m13.MatMul(m35) = matrix m[1,5] \n",m13.MatMul(m35));

   Print("\n3. Product of matrix m[3,5] and vertical vector v[5]");
   vector v5 = {1,2,3,4,5};
   Print("On the left m35 = \n",m35);
   Print("On the right v5 = ",v5);
   Print("m35.MatMul(v5) = vertical vector v[3] \n",m35.MatMul(v5));
//--- show that this is really a vertical vector
   Print("\n4. Product of matrix m[3,5] and matrix m[5,1]");
   matrix m51;
   m51.Init(5,1,Arange,1);
   Print("On the left m35 = \n",m35);
   Print("On the right m51 = \n",m51);
   Print("m35.MatMul(m51) = matrix v[3] \n",m35.MatMul(m51));

   Print("\n5. Product of matrix m[3,5] and matrix m[5,2]");
   Print("On the left m35 = \n",m35);
   Print("On the right m52 = \n",m52);
   Print("m35.MatMul(m52) = matrix m[3,2] \n",m35.MatMul(m52));

   Print("\n6. Product of horizontal vector v[5] and matrix m[5,2]");
   Print("On the left v5 = \n",v5);
   Print("On the right m52 = \n",m52);
   Print("v5.MatMul(m52) = horizontal vector v[2] \n",v5.MatMul(m52));

   Print("\n7. Outer() product of horizontal vector v[5] and vertical vector v[3]");
   Print("On the left v5 = \n",v5);
   Print("On the right v3 = \n",v3);
   Print("v5.Outer(v3) = matrix m[5,3] \n",v5.Outer(v3));
//--- show that the product of matrices generates the same result
   Print("\n8. Outer() product of the matrix m[1,5] and matrix m[3,1]");
   matrix m15,m31;
   m15.Init(1,5,Arange,1);
   m31.Init(3,1,Arange,1);
   Print("On the left m[1,5] = \n",m15);
   Print("On the right m31 = \n",m31);
   Print("m15.Outer(m31) = matrix m[5,3] \n",m15.Outer(m31));
  }
//+------------------------------------------------------------------+
//|  Fill the matrix with increasing values                          |
//+------------------------------------------------------------------+
void Arange(matrix & m, double start = 0, double step = 1) // the function has three parameters
  {
//---
   ulong cols = m.Cols();
   ulong rows = m.Rows();
   double value = start;
   for(ulong r = 0; r < rows; r++)
     {
      for(ulong c = 0; c < cols; c++)
        {
         m[r][c] = value;
         value += step;
        }
     }
//---     
  }
/*
Execution result

1. Product of horizontal vector v[3] and matrix m[3,5]
On the left v3 = [1,2,3]
On the right m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
v3.MatMul(m35) = horizontal vector v[5]
[40,46,52,58,64]

2. Product of matrix m[1,3] and matrix m[3,5]
On the left m13 =
[[1,2,3]]
On the right m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
m13.MatMul(m35) = matrix m[1,5]
[[40,46,52,58,64]]

3. Product of matrix m[3,5] and vertical vector v[5]
On the left m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
On the right v5 = [1,2,3,4,5]
m35.MatMul(v5) = vertical vector v[3]
[40,115,190]

4. Product of matrix m[3,5] and matrix m[5,1]
On the left m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
On the right m51 =
[[1]
 [2]
 [3]
 [4]
 [5]]
m35.MatMul(m51) = matrix v[3]
[[40]
 [115]
 [190]]

5. Product of matrix m[3,5] and matrix m[5,2]
On the left m35 =
[[0,1,2,3,4]
 [5,6,7,8,9]
 [10,11,12,13,14]]
On the right m52 =
[[0,1]
 [2,3]
 [4,5]
 [6,7]
 [8,9]]
m35.MatMul(m52) = matrix m[3,2]
[[60,70]
 [160,195]
 [260,320]]

6. The product of horizontal vector v[5] and matrix m[5,2]
On the left v5 =
[1,2,3,4,5]
On the right m52 =
[[0,1]
 [2,3]
 [4,5]
 [6,7]
 [8,9]]
v5.MatMul(m52) = horizontal vector v[2]
[80,95]

7. Outer() product of horizontal vector v[5] and vertical vector v[3]
On the left v5 =
[1,2,3,4,5]
On the right v3 =
[1,2,3]
v5.Outer(v3) = matrix m[5,3]
[[1,2,3]
 [2,4,6]
 [3,6,9]
 [4,8,12]
 [5,10,15]]

8. Outer() product of the matrix m[1,5] and matrix m[3,1]
On the left m[1,5] =
[[1,2,3,4,5]]
On the right m31 =
[[1]
 [2]
 [3]]
m15.Outer(m31) = matrix m[5,3]
[[1,2,3]
 [2,4,6]
 [3,6,9]
 [4,8,12]
 [5,10,15]]

*/

Para entender melhor o funcionamento interno dos tipos matrix e vector estes exemplos mostram como as matrizes podem ser utilizadas em vez de vetores ou, em outras palavras, que os vetores podem ser representados como matrizes.


Números complexos - tipo complex

Alguns problemas matemáticos exigem um novo tipo de dados - números complexos. Type complex is a structure:
struct complex
  {
   double             real;   // real part
   double             imag;   // imaginary part
  };
O tipo "complex" pode ser passado por valor como parâmetro para funções MQL5 (ao contrário das estruturas usuais, que são passadas apenas por referência). For functions imported from DLLs, the "complex" type must be passed only by reference.

The 'i' suffix is used to describe complex constants:
complex square(complex c)
  {
   return(c*c);
  }
  
void OnStart()
  {
   Print(square(1+2i));  // a constant is passed as a parameter
  }

// will print "(-3,4)" - a string representation of a complex number
Somente operações simples estão disponíveis para números complexos: =, +, -, *, /, +=, -=, *=, /=, ==, !=.

Em breve será adicionado suporte para números complexos em matrizes e vetores, assim como funções matemáticas adicionais: obtenção do valor absoluto, seno, cosseno e muitos outros.