Обсуждение статьи "R-квадрат как оценка качества кривой баланса стратегии" - страница 2

 
Читал по диагонали, но тем не менее - для себя сделал важные выводы. Автору большое спасибо.
 
Спасибо за полезную статью! Перепостил! :-)
 

Рис. 19. Распределение LR-Correlation для 10 000 случайных блужданий


Рис. 20. Распределение R^2 для 10 000 случайных блужданий

Не понимаю, как R^2 может принимать отрицательные значения, что показаны на втором графике? Да и с первым графиком вопросы. Если линейная регрессия построена правильно, то, вроде, КК Пирсона (LR) не должен быть отрицательным. Но на графике это не так. Где ошибаюсь?


Врубился. Нигде не ошибаюсь, просто на графиках кастомные R^2 и LR - умножение на -1 реального значения происходит в случае, если последний элемент числового ряда меньше первого. Хорошо бы об этом перед графиками написать.

 

В статье линейная регрессия считается с ошибкой - через CLinReg::LRLine.

Доказательство

#include <Graphics\Graphic.mqh> 
#include <Math\Stat\Normal.mqh>
#include <Math\Alglib\Alglib.mqh>

// Возвращает Y-значения линии (y(x)=a*x+b)
void GetLine( const double a, const double b, const int Amount, double &Result[] )
{
  ArrayResize(Result, Amount);
  
  for (int i = 0; i < Amount; i++)
    Result[i] = a * i + b;    
}

// Возвращает линейную регрессию через CLinReg::LRLine
void GetLinearRegression( const double &Array[], double &Result[] )
{
  const int Total = ArraySize(Array);
  
  CMatrixDouble XY(Total, 2);
  
  for (int i = 0; i < Total; i++)
  {
    XY[i].Set(0, i);
    XY[i].Set(1, Array[i]);
  }
  
  int retcode;
  double a, b;
  
  CLinReg::LRLine(XY, Total, retcode, a, b);

  GetLine(a, b, Total, Result);    
}

// Возвращает линейную регрессию через CAlglib::LRBuild + CAlglib::LRUnpack
void GetLinearRegression2( const double &Array[], double &Result[] )
{
  const int Total = ArraySize(Array);
  
  CMatrixDouble XY(Total, 2);
  
  for (int i = 0; i < Total; i++)
  {
    XY[i].Set(0, i);
    XY[i].Set(1, Array[i]);
  }
  
  int retcode;
  
  CLinearModelShell lm;
  CLRReportShell    ar;
//--- массивы для хранения результатов регрессии
  double lr_coeff[];
//--- вычисление коэффициентов линейной регрессии
  CAlglib::LRBuild(XY, Total, 1, retcode, lm, ar);
//--- получение коэффициентов линейной регрессии
  CAlglib::LRUnpack(lm, lr_coeff, retcode);

  GetLine(lr_coeff[0], lr_coeff[1], Total, Result);      
}

void ToChart( const double &Array1[], const double &Array2[], const int X = 0, const int Y = 0, const int Width = 780, const int Height = 380 )
{
  static const string Name = __FILE__;
  
  CGraphic Graphic; 

  if (ObjectFind(0, Name) < 0) 
    Graphic.Create(0, Name, 0, X, Y, Width, Height); 
  else 
    Graphic.Attach(0, Name); 

  Graphic.CurveAdd(Array1, CURVE_LINES);
  Graphic.CurveAdd(Array2, CURVE_LINES);
  
  Graphic.CurvePlotAll(); 
  Graphic.Update();  
}

void GetRandomArray( double &Array[], const int Amount = 1e3 )
{
  double Random[];
  
  MathSrand(GetTickCount()); 

  MathRandomNormal(0, 1, Amount, Random); 
  MathCumulativeSum(Random, Array);
}

#define TOSTRING(A) #A + " = " + (string)(A) + "\n"

void OnStart() 
{   
  double Array[];
  
  GetRandomArray(Array);  
  
  double Estimate[];
  double Estimate2[];
     
  GetLinearRegression(Array, Estimate);
  GetLinearRegression2(Array, Estimate2);

  const double R = CAlglib::PearsonCorr2(Array, Estimate);
  const double R2 = CAlglib::PearsonCorr2(Array, Estimate2);
  
  Print(TOSTRING(R) +
        TOSTRING((Array[0] > Array[ArraySize(Array) - 1]) ? -R : R) +
        TOSTRING(R2));
  
  ToChart(Array, Estimate2);
}


Результат

R = -0.5864718581193301
(Array[0]>Array[ArraySize(Array)-1])?-R:R = -0.5864718581193301
R2 = 0.58647185811933


Знак неверный. Альтернативная реализация ЛР (CAlglib::LRBuild + CAlglib::LRUnpack) считает правильно:


 
fxsaber:

Графики распределений LR Correlation и R^2 для 10 000 независимых примеров, что представлены в статьей, показывают, что R^2 != LR^2.

Не понимаю, почему вторая степень исходного "вогнутого" распределения делает его "ровным"?

Здесь я оказался неправ. Для меня совсем неочевидное утверждение

Удивительно то, что простым математическим действием (возведением во вторую степень) мы полностью убрали нежелательные краевые эффекты распределения.

Поэтому решил экспериментально подтвердить через анимацию (не верить же на слово)

#include <Graphics\Graphic.mqh> 
#include <Math\Stat\Normal.mqh>
#include <Math\Alglib\Alglib.mqh>

// Возвращает Y-значения линии (y(x)=a*x+b)
void GetLine( const double a, const double b, const int Amount, double &Result[] )
{
  ArrayResize(Result, Amount);
  
  for (int i = 0; i < Amount; i++)
    Result[i] = a * i + b;    
}

// Возвращает линейную регрессию через CAlglib::LRBuild + CAlglib::LRUnpack
void GetLinearRegression( const double &Array[], double &Result[] )
{
  const int Total = ArraySize(Array);
  
  CMatrixDouble XY(Total, 2);
  
  for (int i = 0; i < Total; i++)
  {
    XY[i].Set(0, i);
    XY[i].Set(1, Array[i]);
  }
  
  int retcode;
  
  CLinearModelShell lm;
  CLRReportShell    ar;
//--- массивы для хранения результатов регрессии
  double lr_coeff[];
//--- вычисление коэффициентов линейной регрессии
  CAlglib::LRBuild(XY, Total, 1, retcode, lm, ar);
//--- получение коэффициентов линейной регрессии
  CAlglib::LRUnpack(lm, lr_coeff, retcode);

  GetLine(lr_coeff[0], lr_coeff[1], Total, Result);      
}

// Вычисляет R
double GetCustomR( const double &Array[] )
{
  double Estimate[];
   
  GetLinearRegression(Array, Estimate);
   
  const double R = CAlglib::PearsonCorr2(Array, Estimate);

  return((Array[0] > Array[ArraySize(Array) - 1]) ? -R : R);
}

// Вычисляет R случайных векторов
void GetRandomCustomR( const int Amount, const int VectorSize, double &Result[] )
{
  double Random[];
  double Sum[];
  
  MathSrand(GetTickCount()); 

  ArrayResize(Result, Amount);
  
  for (int i = 0; i < Amount; i++)
  {
    MathRandomNormal(0, 1, VectorSize, Random); 
    MathCumulativeSum(Random, Sum);
    
    Result[i] = GetCustomR(Sum);
  }  
}

void ToChart( const double &X[],  const double &Y[], const string Str = NULL, const int X0 = 0, const int Y0 = 0, const int Width = 780, const int Height = 380 )
{
  static const string Name = __FILE__;
  
  CGraphic Graphic; 

  if (ObjectFind(0, Name)<0) 
    Graphic.Create(0, Name, 0, X0, Y0, Width, Height); 
  else 
    Graphic.Attach(0, Name); 

  Graphic.BackgroundMain(Str); 
  Graphic.BackgroundMainSize(16); 

  Graphic.CurveAdd(X, Y, CURVE_HISTOGRAM).HistogramWidth(6);
  
  Graphic.CurvePlotAll(); 
  Graphic.Update();  
}

void MathPow( double &Result[], const double &Array[], const double Pow )
{
  const int Size = ArrayResize(Result, ArraySize(Array));
  
  for (int i = 0; i < Size; i++)
    Result[i] = (Array[i] < 0) ? -MathPow(-Array[i], Pow) : MathPow(Array[i], Pow);
}

// https://www.mql5.com/ru/docs/standardlibrary/mathematics/stat/normal
//+------------------------------------------------------------------+ 
//|  Calculate frequencies for data set                              | 
//+------------------------------------------------------------------+ 
bool CalculateHistogramArray(const double &data[],double &intervals[],double &frequency[], 
                             double &maxv,double &minv,const int cells=10) 
  { 
   if(cells<=1) return (false); 
   int size=ArraySize(data); 
   if(size<cells*10) return (false); 
   minv=data[ArrayMinimum(data)]; 
   maxv=data[ArrayMaximum(data)]; 
   double range=maxv-minv; 
   double width=range/cells; 
   if(width==0) return false; 
   ArrayResize(intervals,cells); 
   ArrayResize(frequency,cells); 
//--- зададим центры интервалов 
   for(int i=0; i<cells; i++) 
     { 
      intervals[i]=minv+(i+0.5)*width; 
      frequency[i]=0; 
     } 
//--- заполним частоты попадания в интервал 
   for(int i=0; i<size; i++) 
     { 
      int ind=int((data[i]-minv)/width); 
      if(ind>=cells) ind=cells-1; 
      frequency[ind]++; 
     } 
   return (true); 
  } 

void DistributionToChart( const double &Array[], const string Str = NULL, const int NCells = 51 )
{
  double X[];          // центры интервалов гистограммы 
  double Y[];          // количество значений из выборки, попавших в интервал 
  double Max, Min;     // максимальное и минимальное значения в выборке 
  
  CalculateHistogramArray(Array, X, Y, Max, Min, NCells);   

  ToChart(X, Y, Str);
}

void OnInit() 
{   
  double R[];
  
  GetRandomCustomR(1e3, 1e4, R);
  
  double Array[];

  const int Max = 50;
  
  while (!IsStopped())
    for (int i = 1; !IsStopped() && i < (Max << 1); i++)
    {
      const double Pow = (i > Max) ? ((Max << 1) - i) * 0.1 : i * 0.1;
      
      MathPow(Array, R, Pow);
      DistributionToChart(Array, "Distribution of R^" + DoubleToString(Pow, 1));      
      
      Sleep(100);
    }
}



Похоже, все так и есть.

 

 

Рис. 21. Значение R^2 в качестве пользовательского критерия оптимизации

А где в MQL LR Correlation, что на картинке? Или этот и многие другие показатели рассчитываются только для одиночных прогонов, поэтому отсутствуют в ENUM_STATISTICS?

Если так, то предлагают этот параметр рассчитывать из обоснованных соображений, озвученных в данной статье: по эквити без ММ и в квадрате.


ЗЫ Замерил, сколько времени уходит на расчет GetCustomR для массива на миллион значений (типа эквити) - 2.5 секунды. Дофига, короче. Все уходит на вычисление ЛР (CAlglib::LRBuild + CAlglib::LRUnpack). Зато иногда кривая ЛР через CLinReg::LRLine на порядок быстрее. Если ее допилить, то, как критерий Оптимизации, станет терпимым при Оптимизациях.

Документация по MQL5: Стандартные константы, перечисления и структуры / Состояние окружения / Статистика тестирования
Документация по MQL5: Стандартные константы, перечисления и структуры / Состояние окружения / Статистика тестирования
  • www.mql5.com
Максимальная просадка баланса в процентах. В процессе торговли баланс может испытать множество просадок, для каждой фиксируется относительное значение просадки в процентах. Возвращается наибольшее значение Максимальная...
 
Dennis Kirichenko:

О как! Всегда думал, что 100. Спасибо, интересная статья.

Да, это число я встречал в авторитетных книжках по R и статистике. Но жаль не смог найти ссылку, так что сорри.

Dennis Kirichenko:

Ещё принято проводить тесты на значимость коэффициента регрессии. Даже в Алглиб они есть :-)

Очевидно что тесты для нормального распределения. У нас равномерное получилось.

PearsonCorrelationSignificance(), SpearmanRankCorrelationSignificance()

За ссылку спасибо, запомню.

 
fxsaber:

ЗЫ Неверное утверждение

R^2 — не что иное, как корреляция между графиком и его линейной моделью

//-- Находим R^2 и его знак
   double r2 = MathPow(corr, 2.0);

Да, действительно, грубая ошибка в формулировке. Даже удиветльно, что я такое написал. Исправлю.

Когда смотришь все остальные MQL-коды, то не понимаешь, зачем они приведены, т.к. они совершенно не читабельны без владения CStrategy

CStrategy нужен только для сбора еквити. Основные код как правильно было замечено это собственно рассчет R2

Код для расчета "эквити", годного для R^2. Написан в MT4-стиле, в MT5 перевести несложно...

Изучим.

 
Maxim Dmitrievsky:

С этим согласен, все остальное придется выдирать из классов, для добавления в свою систему.. лучше бы все в отдельных ф-ях было или отдельный инклудник.

Берете свой рассчет еквити (или тот код, что представлен fxsaber'ом) в виде дабл-массива и вставляете в функцию рассчета R^2. Ничего выдирать не надо, не надо исползовать классы и CStrategy

 
fxsaber:

Не понимаю, как R^2 может принимать отрицательные значения, что показаны на втором графике? Да и с первым графиком вопросы. Если линейная регрессия построена правильно, то, вроде, КК Пирсона (LR) не должен быть отрицательным. Но на графике это не так. Где ошибаюсь?

Врубился. Нигде не ошибаюсь, просто на графиках кастомные R^2 и LR - умножение на -1 реального значения происходит в случае, если последний элемент числового ряда меньше первого. Хорошо бы об этом перед графиками написать.

Запрятано в статье:

Наш скрипт рассчитывает как LR Correlation, так и R^2. Разницу между ними мы увидим чуть позже. В скрипт внесено небольшое дополнение. Получившийся коэффициент корреляции мы будем умножать на итоговый знак синтетического графика. Если мы закончили с результатом меньше нуля, корреляция будет отрицательной, если больше — положительной. Сделано это для того, чтобы легко и быстро отделить отрицательные исходы от положительных, не прибегая при этом к другим статистикам. Именно так работает LR Correlation в MetaTrader 5, по такому же принципу будет строиться и R^2.

Наверное надо было написать об этом в другом месте и несколько раз.