//+------------------------------------------------------------------+
//|                                                        nnetw.mq5 |
//|                                                А. Б. Макаровский |
//|                                             https://www.andmk.ru |
//+------------------------------------------------------------------+
#property copyright "А. Б. Макаровский"
#property link      "https://www.andmk.ru"
#property version   "1.00"

#define THREADS_NUM 4 ;Количество процессоров (ядер) на компьютере

#import "neuralnetw.dll"

//Создание нейронной сети
//Параметры:
//int n - количество входов
//int _nlayers - число слоев, первым слоем считается массив входных данных,
//поэтому, если например нужно создать сеть с двумя слоями, _nlayers должно быть равно 3
//int _pqnt - количество обучающих образцов
//int _nthreads - количество нитей, т.е. процессов, которые будут выполняться на различных процессорах (ядрах),
//можно установить равным числу имеющихся на компьютере процессорах (ядрах)
//double _rand_epsilon - константа для инициализации матриц весов слоев нейронной сети, обычно 0.12
//Начальные значения элементов матриц весов вычисляются по формуле:
//(((double)rand()) / RAND_MAX) * 2 * rand_epsilon - rand_epsilon,
//(((double)rand()) / RAND_MAX) является псевдослучайным числом в диапазоне от 0 до 1
//int &nout[] - массив, содержащий число выходов у каждого слоя нейронной сети, кроме первого слоя, число выходов которого равно n
//int _tracebufcnt - размер буфера трассировки, если он равен нулю, то буфер трассировки использоваться не будет
//char &_tracebuffer[] - буфер трассировки, туда нейронная сеть записывает отладочную информацию
//Возвращаемое значение: число long, которое фактически является указателем на созданный объект нейронной сети
long nnCreate(int n, int _nlayers, int _pqnt, int _nthreads, double _rand_epsilon, int &nout[], int _tracebufcnt, char &_tracebuffer[]);

//Создание нейронной сети из образа, который был сохранен в файле string fname
long nnLoad(string fname);

//Удаление объекта нейронной сети
//Параметры:
//long nnet - указатель на объект нейронной сети
int nnDelete(long nnet);

//Загрузка в нейронную сеть обучающих образцов
//Параметры:
//long nnet - указатель на объект нейронной сети
//double &_X[] - входы нейронной сети для каждого обучающего образца
//double &_Y[] - выходы нейронной сети для каждого обучающего образца
//Обучающие образцы должны располагаться в этих массивах последовательно
//Размерность массива _X - _pqnt * n, размерность массива _Y - _pqnt * nout[_nlayers - 2], где
//_nlayers, n, nout, _pqnt - параметры, ранее переданные в функцию NNCreate
void nnSetPatterns(long nnet, double &_X[], double &_Y[]);

//Обучение нейронной сети
//Параметры:
//long nnet - указатель на объект нейронной сети
//int maxiter - максимальное количество итераций, обычно порядка 1000
//double lambda - коэффициент, с которым учитывается при каждой итерации градиент 
//в алгоритме обратного распространения ошибок, обычно 1.0 - 2.0
//double epsilon - константа для остановки алгоритма обучения, может быть равна, например, 0.02,
//если сумма квадратов всех элементов градиентов всех слоев нейронной сети, деленная на количество обучающих образцов
//(в буфере трассировки обозначена norma) становится меньше epsilon, обучение останавливается
//double lambdareg - обычно равно нулю, если проверка качества обучения нейронной сети будет проводиться на специальном тестовом наборе данных,
//можно включить регуляризацию, задав параметр lambdareg порядка 1.0 - 2.0 (подбирается экспериментально). Цель - устранение переобучения,
//т.е. излишнего приспособления нейронной сети к набору обучающих образцов. Параметр lambdareg нужно подбирать так, чтобы получить
//возможно лучшее распознавание образцов на тестовом наборе данных (проверять нужно уже после обучения с помощью функции Recognize,
//можно проверить качество обучения на обучающем наборе данных - посчитать, сколько образцов будет распознано неправильно, но более
//точные результаты можно получить, если для этого использовать специальный тестовый набор данных, обучение на котором не производилось)
double nnLearn(long nnet, int maxiter, double lambda, double epsilon, double lambdareg);

//Распознавание образцов
//Параметры:
//long nnet - указатель на объект нейронной сети
//double &_X[] - входной образец
//double &_Y[] - в этом массиве нейронная сеть вернет результаты распознавания (массив выходов нейронной сети)
void nnRecognize(long nnet, double &_X[], double &_Y[]);

//Сохранение образа обученной нейронной сети в файле
//Параметры:
//long nnet - указатель на объект нейронной сети
//string fname - имя файла, должно соответствовать требованиям торгового терминала MetaTrader (если функция вызывается из
//программы, написанной на языке MQL5, в противном случае таких требований нет)
int nnSave(long nnet, string fname);

//Получить количество выходов нейронной сети
//Параметры:
//long nnet - указатель на объект нейронной сети
//Возвращаемое значение: количество выходов нейронной сети
int nnGetOutQnt(long nnet);

//Функции объекта подготовки данных

//Создание объекта подготовки данных
//При этом происходит нормализация исходных данных стандартным способом, чтобы они находились в диапазоне от 0 до 1
//Сначала рассчитываются максимальное и минимальное значение для всего обучающего массива, на основе этого нормализуется
//информация каждого обучаещего образца по формуле (x - min) / (max - min), затем рассчитывается
//максимальное и минимальное значение для него pmax и pmin и к нему добавляется еще одно значение - (pmax - pmin) / (max - min)
//Таким образом, размерность каждого обучающего образца, который буден подан на вход нейронной сети, становится на 1 больше
//Это много времени не занимает, и можно такую работу сделать в вызывающей программе, а объект подготовки данных не создавать
//Массив выходов нейронной сети для каждого обучающего образца при нормализации не меняется
//Копии массивов x и y сохраняются в объекте подготовки данных и обрабатываются эти копии, затем их нужно вернуть
//в вызывающую программу
//Параметры:
//int _qnt - количество обучающих образцов
//int _n_in - размерность массива одного образца, подаваемого на вход нейронной сети
//int _n_out - количество выходов нейронной сети
//double &x[] - массив обучающих образцов, подаваемых на вход нейронной сети
//Возвращаемое значение - число long, которое является адресом созданного объекта подготовки данных
long nndpCreate(int _qnt, int _n_in, int _n_out, double &x[]);

//Получить новую размерность массива одного образца, подаваемого на вход нейронной сети
//При нормализации данных в объекте подготовки данных эта размерность может измениться
//Параметры:
//long dp - адрес созданного объекта подготовки данных
//Возвращаемое значение - новая размерность массива одного образца, подаваемого на вход нейронной сети
int nndpGetQnt(long dp);

//Получить массив обучающих образцов, подаваемых на вход нейронной сети, после нормализации
//Параметры:
//long dp - адрес созданного объекта подготовки данных
//double &x[] - массив обучающих образцов, подаваемых на вход нейронной сети, после нормализации (возвращается функцией)
void nndpGetX(long dp, double &x[]);

//Получить максимальное значение данных до нормализации
//Параметры:
//long dp - адрес созданного объекта подготовки данных
//Возвращаемое значение - максимальное значение данных до нормализации
double nndpGetMax(long dp);

//Получить минимальное значение данных до нормализации
//Параметры:
//long dp - адрес созданного объекта подготовки данных
//Возвращаемое значение - минимальное значение данных до нормализации
double nndpGetMin(long dp);

//Нормализация одного обучающего образца
//Параметры:
//long dp - адрес созданного объекта подготовки данных
//double &in[] - образец, подаваемый на вход нейронной сети
//double &out[] - образец, подаваемый на вход нейронной сети, после нормализации (возвращается функцией)
//Размерность массива out[] должна соответствовать возвращаемой функцией GetDataPrepareQnt
void nndpNormalyze(long dp, double &in[], double &out[]);

//Удаление объекта подготовки данных
//Параметры:
//long dp - адрес созданного объекта подготовки данных
void nndpDelete(long dp);

#import

struct JBuffer
  {
public:
   //Данные таймфрейма
   double            PriceOpen;              
   double            PriceHigh;              
   double            PriceLow;               
   double            PriceClose;             
   };
   
//--- input parameters
input int      TakeProfit=2;
input int      StopLoss=2;
sinput string  UsedSymbols    =  "EURUSD,AUDUSD,EURAUD,EURGBP,EURCAD,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";
sinput string  UsedTimeframes =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1";
input int      OrderValue=0;

long nnet;
long dataobj;
int nout[3];
char tbuf[100000];
string fn = "test.dat";
string fname;
datetime first_date;

//JBuffer jbuf1[5000*50];

double PriceOpen[100000];              
double PriceHigh[100000];              
double PriceLow[100000];               
double PriceClose[100000]; 
   
#define LPMAX 600000
#define TPMAX 300000
#define LPMAX_OUT 50000
#define TPMAX_OUT 20000

double LearnPatterns[];
double LearnPatternsRes[];
double TestPatterns[];
double TestPatternsRes[];
double LearnPatternsOut[LPMAX_OUT];
double TestPatternsOut[TPMAX_OUT];
double X0[1000];
double Y0[1000];
    
int lpqnt = 0;
int tpqnt = 0;
int lpoutqnt = 0;
int tpoutqnt = 0;
int flagcnn = 0;
bool bEventStudy = false;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   nout[0] = 25;
   nout[1] = 2;
   nout[2] = 0;
   
   string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);
   fname = terminal_data_path + "\\MQL5\\Files\\" + fn;
   
   ArrayResize(LearnPatterns, LPMAX);
   ArrayResize(LearnPatternsRes, (LPMAX / 200) * 201);
   ArrayResize(TestPatterns, TPMAX);
   ArrayResize(TestPatternsRes, (TPMAX / 200) * 201);
   
   bEventStudy=EventChartCustom(ChartID(),1,0,0,"Init");

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   //nnetw::NNDelete(nnet);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

//---
   return;  
  }
  
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
//---
   if (lparam == 1)
   {
   nout[0] = 25;
   nout[1] = 2;
   nout[2] = 0;
   
   int ptype = 0;
      
   Alert("OnChartEvent");
   
   if (SaveTickFiles(fn) == -1)
   {
       Alert("Не удалось загрузить данные из файла ", fn);
       return;
   }
     
   //Формирование обучающих и тестовых образцов
   int pattertcnt = 0;
   for(int i = 50; i < (100000 - 50); i++)
   {
       //Формирование образцов для опционов на повышение цены
       double phstart = PriceHigh[i];
       if (PriceLow[i + 30] - 0.005 > phstart)
       {
           if (ptype < 3)
           {
               CreateLearnPattern(i - 50, 1);
               i += 4;
               ptype++;
               pattertcnt++;
           }
           else
           {
               CreateTestPattern(i - 50, 1);
               i += 4;
               pattertcnt++;
               ptype = 0;
           }
       }
       
       //Формирование образцов для опционов на понижение цены
       phstart = PriceLow[i];
       if (PriceHigh[i + 30] + 0.005 < phstart)
       {
           if (ptype < 3)
           {
               CreateLearnPattern(i - 50, 0);
               i += 4;
               ptype++;
               pattertcnt++;
           }
           else
           {
               CreateTestPattern(i - 50, 0);
               i += 4;
               pattertcnt++;
               ptype = 0;
           }
       }
       
       if (pattertcnt >= 2800)
       {
            break;
       }       
   }
   
   Alert("Точка 1: lpoutqnt=", lpoutqnt, ", tpoutqnt=", tpoutqnt);
   
   dataobj = CreateDP(lpoutqnt / 2, 200, 2, LearnPatterns, LearnPatternsRes);
   if (dataobj == 0)
   {
       Alert("Объект данных создать не удалось");
       return;
   }
   
   double gmax = nndpGetMax(dataobj);
   double gmin = nndpGetMin(dataobj);
   
   Alert("LearnPatterns подготовлен, gmax = ", gmax, ", gmin = ", gmin);
   nndpDelete(dataobj);
   dataobj = 0;
   
   dataobj = CreateDP(tpoutqnt / 2, 200, 2, TestPatterns, TestPatternsRes);
   if (dataobj == 0)
   {
       Alert("Объект данных создать не удалось");
       return;
   }
   
   gmax = nndpGetMax(dataobj);
   gmin = nndpGetMin(dataobj);  
    
   Alert("TestPatterns подготовлен, gmax = ", gmax, ", gmin = ", gmin);
   nndpDelete(dataobj);
   dataobj = 0;
         
   nnet = CreateNN(201, 3, lpoutqnt / 2, tpoutqnt / 2, 0.12, nout, 100000, tbuf);
   if (nnet == 0)
   {
       Alert("Нейронную сеть создать не удалось");
       return;
   }
      
   string stbuf = CharArrayToString(tbuf);
   int pos = StringFind(stbuf, "Iteration 1400");
   if (pos > 0)
   {
       Alert(StringSubstr(stbuf, pos));
   }
   
   bEventStudy = true;
   //Alert(CharArrayToString(tbuf));
   

   //nnetw::Save(nnet, fn);
     

   //Alert(CharArrayToString(tbuf));
   }
}  
//+------------------------------------------------------------------+

void CreateLearnPattern(int start, int type)
{
    if (type == 1)
    {
        LearnPatternsOut[lpoutqnt++] = 1;
        LearnPatternsOut[lpoutqnt++] = 0;
    }
    else
    {
        LearnPatternsOut[lpoutqnt++] = 0;
        LearnPatternsOut[lpoutqnt++] = 1;    
    }

    for(int i = 0; i < 50; i++)
    {
        LearnPatterns[lpqnt++] = PriceOpen[start + i];
    }
    
    for(int i = 0; i < 50; i++)
    {
        LearnPatterns[lpqnt++] = PriceClose[start + i];
    }
    
    for(int i = 0; i < 50; i++)
    {
        LearnPatterns[lpqnt++] = PriceHigh[start + i];
    }
    
    for(int i = 0; i < 50; i++)
    {
        LearnPatterns[lpqnt++] = PriceLow[start + i];
    }        
}

void CreateTestPattern(int start, int type)
{
    if (type == 1)
    {
        TestPatternsOut[tpoutqnt++] = 1;
        TestPatternsOut[tpoutqnt++] = 0;
    }
    else
    {
        TestPatternsOut[tpoutqnt++] = 0;
        TestPatternsOut[tpoutqnt++] = 1;    
    }

    for(int i = 0; i < 50; i++)
    {
        TestPatterns[tpqnt++] = PriceOpen[start + i];
    }
    
    for(int i = 0; i < 50; i++)
    {
        TestPatterns[tpqnt++] = PriceClose[start + i];
    }
    
    for(int i = 0; i < 50; i++)
    {
        TestPatterns[tpqnt++] = PriceHigh[start + i];
    }
    
    for(int i = 0; i < 50; i++)
    {
        TestPatterns[tpqnt++] = PriceLow[start + i];
    }    
}

long CreateDP(int qnt, int n_in, int n_out, double &X[], double &Xp[])
{
    long datapobj = 0;
   
    datapobj = nndpCreate(qnt, n_in, n_out, X);
    
    if (datapobj == 0)
    {
        Alert("Объект подготовки данных создать не удалось");
        
        return 0;
    }

    nndpGetX(datapobj, Xp);
    
    return datapobj;
}

long CreateNN(int n, int nlayers, int qnt, int tqnt, double rand_epsilon, int &_nout[], int tbcnt, char &tb[])
{
    long obj = 0;
    double Rights = 0;
    
    Alert("CreateNN: n= ", n, " nlayers = ", nlayers, " qnt = ", qnt, " tqnt = ", tqnt);
    
    obj = nnCreate(n, nlayers, qnt, THREADS_NUM, rand_epsilon, _nout, tbcnt, tb);

    if (obj == 0)
    {
        Alert("Нейронную сеть создать не удалось");
        
        return 0;
    }
    
    nnSetPatterns(obj, LearnPatternsRes, LearnPatternsOut);

    nnLearn(obj, 1500, 1, 0.02, 0);

    Alert("Нейронная сеть обучена");
    Alert("Количество выходов нейронной сети = ", nnGetOutQnt(obj));
    
    //string fname = "nnetwsave.bin";

    //Test1(obj);
        
    for (int i = 0; i < tqnt; i++)
    {
        for(int j = 0; j < n; j++)
        {
            X0[j] = TestPatternsRes[i * n + j];
        }
        
        nnRecognize(obj, X0, Y0);
            
        Rights += CheckResult(i);
    }
        
    Alert("Количество правильно распознанных образцов = ", Rights, " Всего образцов = ", tqnt);
    
    return obj;
}

int CheckResult(int i)
{
        int im1 = 0, im2 = 0;
        int m = 2;
        double m1 = -1, m2 = -1;
        
        for (int j = 0; j < m; j++)
        {
            if (Y0[j] > m1)
            {
                m1 = Y0[j];
                im1 = j;
            }
        }
        
        for (int j = 0; j < m; j++)
        {
            if (TestPatternsOut[i * m + j] > m2)
            {
                m2 = TestPatternsOut[i * m + j];
                im2 = j;
            }
        }
                
        if (im1 == im2)
        {
            return(1);
        }
        
        return(0);
}

int LoadTickFiles(string filename)
{
   int fh=FileOpen(filename,FILE_READ|FILE_BIN|FILE_ANSI);
   
   if(fh<0)
     {
      Alert("Неудачная попытка открыть файл по абсолютному пути");
      Alert(GetLastError());
      return(-1);
     }

    FileReadArray(fh,PriceOpen);
    FileReadArray(fh,PriceClose);
    FileReadArray(fh,PriceHigh);
    FileReadArray(fh,PriceLow);
    
    FileClose(fh);
    
    return(INIT_SUCCEEDED);
}

int SaveTickFiles(string filename)
{
    if(!SymbolInfoInteger("EURUSDrfd",SYMBOL_SELECT))
     {
      if(GetLastError()==ERR_MARKET_UNKNOWN_SYMBOL) return(-1);
      SymbolSelect("EURUSDrfd",true);
     }
   SeriesInfoInteger("EURUSDrfd",PERIOD_M30,SERIES_FIRSTDATE,first_date);
   
   Alert(CopyOpen("EURUSDrfd", PERIOD_M15, 0, 100000, PriceOpen));
   Alert(CopyClose("EURUSDrfd", PERIOD_M15, 0, 100000, PriceClose));
   Alert(CopyHigh("EURUSDrfd", PERIOD_M15, 0, 100000, PriceHigh));
   Alert(CopyLow("EURUSDrfd", PERIOD_M15, 0, 100000, PriceLow));   
      
   /*int fh=FileOpen(filename,FILE_WRITE|FILE_BIN|FILE_ANSI);
   
   if(fh<0)
     {
      Alert("Неудачная попытка открыть файл по абсолютному пути");
      Alert(GetLastError());
      return(-1);
     }
     
    FileWriteArray(fh,PriceOpen);
    FileWriteArray(fh,PriceClose);
    FileWriteArray(fh,PriceHigh);
    FileWriteArray(fh,PriceLow);
    
    FileClose(fh);*/
    
    return(0);
}
