Статьи по MQL4
Работа с массивами

Работа с массивами


например, forex

Есть две новости - плохая и хорошая. Плохая - рынок предсказать нельзя.
Хорошая - чтобы зарабатывать деньги, этого и не нужно
Брюс Бэбкок



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

В каждой шутке есть доля шутки. Но шутка продолжается. Периодически мы слышим, что можно зарабатывать на случайном блуждании цены, при этом нет необходимости в разработке торговой системы, а требуется только знание математики в пределах первых курсов ВУЗа. Выкладываются результаты работы торговых систем на случайных котировках, результаты тестирования подтверждают – на случайном рынке зарабатывать можно и случайным образом. Я создал таблицу в Excel, которая моделирует 100 сделок на ценах с приращением по закону нормального распределения, и построил 7 столбцов, каждый из которых показывает итоговую прибыль из 20 серий по 100 сделок от заданных StopLoss(SL) и TakeProfit(TP).

 
 


Видно, что прибыль при соотношении TP/SL=3 получилась максимальной (472), а при обратном отношении максимальным является убыток (-492). Вы можете провести свои серии испытаний, чтобы сравнить с тем, что получилось у меня. Для этого вы меняете настройки и 20 раз записываете значение «Итога» в нужные ячейки

 
 


Возможно, ваши результаты будут совсем не такими, как у меня, но если они окажутся близки хотя бы наполовину? Что делать в этом случае, сразу начинать торговать по принципу случайного открытия сделки с соотношением TP/SL=3 или сначала убедиться – являются ли цены рынка нормально распределенными?

Нормальные распределения считаются наиболее важными в методах математической статистики, рекомендую почитать книгу С.В. Булашева «Статистика для трейдера». Я попробую своими словами объяснить что такое нормальное распределение. Пусть у нас имеется два спортсмена-стрелка, каждый из которых производит по 100 выстрелов в мишень. Мы будем замерять на какое расстояние от центра («десятки») отклонилась пуля. Например, в сантиметрах. Подсчитав все 100 выстрелов, мы получим примерно такую таблицу.

 
 


Видно, что частота распределения попаданий имеет колоколообразную форму с пиком в районе 0. Для нормальных распределений центр совпадает с математическим ожиданием (арифметическое среднее). Кроме того, колокол может иметь пологие края или более крутые. Это характеризуется разбросом случайной величины вокруг среднего. Критерием этого разброса является стандартное отклонение (σ - сигма), которое мы уже знаем как находить. Из двух стрелков в общем случае лучшим будет тот, у которого стандартное отклонение меньше.

Одним из важных свойств нормального распределения является то, что в интервале 3 сигмы от математического ожидания находятся 99,73% значений, в интервале +- 2 сигмы - 95,45 %, и в интервале +- сигма – 68.27 % . Мы попробуем изучить распределение цен закрытия для любого инструмента с помощью скрипта.

Скрипт будет заполнять массив CloseArray[] значениями Close[i]-Close[i+N], где N я назвал смещением. То есть, мы будем смотреть, как будет меняться распределение приращений при различных N, при N от 1 до 10. Каждое такое заполнение массива я назвал выборкой, каждая выборка будет иметь такие характеристики, как среднее значение (матожидание), стандартное отклонение(сигма), максимальное и минимальное значение, а также процент попадания в одну, две и три сигмы. Эти характеристики будут выводиться в файл *.csv, чтобы потом по ним можно было построить диаграммы распределения.

Для записи основных характеристик служит массив Stat[8]:

 
 


Таким образом, мы можем обработать значения Close[i]-Close[i+1] на тайм-фрейме D1. Получается, что для обработки Close[i]-Close[i+2] нам нужно либо создать новый массив Stat2[] либо очистить значения массива Stat[] и заново заполнить. Такой способ не является универсальным, и для таких случаев служат многомерные массивы. Мы лучше объявим массив Stat[10][8], где будем хранить для каждого смещения от 1 до 10 свои 8 характеристик.

 
 


Теперь мы имеем двухмерный массив. Но остался еще один момент – возможно, нам потребуется получить статистику не только для дневных баров (D1), но и для других тайм-фреймов. Добавим универсальности с помощью еще одного измерения :

 
 


Массивы в MQL-4 могут быть не более, чем 4-мерными, что хватает для большинства задач. Теперь мы можем в цикле проходить по 7 тайм-фреймам (от PERIOD_M1 до PERIOD_D1) и 10 смещениям. Так как индексы по первому измерению могут принимать значения от 0 до 6, то я создал функцию, которая возвращает по номеру тайм-фрейма значение тайм-фрейма в минутах, чтобы это можно было использовать в стандартных функциях.

 
 


Сами формулы предельно просты, но код получился немного длинноват.


ссылка по теме http:www.statsoft.ru/home/portal/glossary/GlossaryTwo/N/NormalDistribution.htm
#property copyright "MetaQuotes" #property link "http://www.alpari-idc.ru/ru/experts/articles/"
int FileHandle;
int init() { string FileName; Print("Выполняем init()"); // сформируем имя файла, например , FileName=Symbol()+"CloseStat.csv"; //откроем файл с именем FileName (создадим указатель/handle на него) FileHandle=FileOpen(FileName,FILE_WRITE | FILE_CSV,";"); if (FileHandle<1) { Print("Не удалось открыть файл, ошибка ",GetLastError()); return; } return(0); } int deinit() { Print("Выполняем deinit()");
//закроем файл (освободим указатель/handle, чтобы файл можно было //открыть для редактирования другими программами) if(FileHandle>0) FileClose(FileHandle); return(0); } int start() { double CloseArray[]; double Stat[7,10,8]; // 7 тайм-фреймов, 10 смещений, 8 характеристик // 0 - Число записей для периода и смещения // 1 - Среднее в пунктах // 2 - Max в пунктах // 3 - Min в пунктах // 4 - Станд. отклонение в пунктах // 5 - процент попаданий в интервал +- sigma 6 - ------ +- 2 sigma 7 - ------ +- 3 sigma string rangeDescription[14]; double range[13]; int freequency[14]; int curPeriod; double Summ,Average,StDev,begin; int MaxOnArray,MinOnArray; int i_period,shift,m,z,t; double sigma,sigma_2,sigma_3; int BarsPeriod;
for (i_period=0;i_period<7;i_period++) // проход по таймфреймам { curPeriod=PeriodNumber(i_period); BarsPeriod=iBars(NULL,curPeriod); for (shift=1;shift<=10;shift++) // проход по смещениям { ArrayResize(CloseArray,BarsPeriod-shift); ArraySetAsSeries(CloseArray,true); MaxOnArray=-10000; MinOnArray=10000; Summ=0.0; StDev=0.0; for (m=0;m<BarsPeriod-shift;m++) // проход по барам { CloseArray[m]=(iClose(NULL,curPeriod,m)-iClose(NULL,curPeriod,m+shift))/Point; if (CloseArray[m]>MaxOnArray) MaxOnArray=CloseArray[m]; if (CloseArray[m]<MinOnArray) MinOnArray=CloseArray[m]; Summ+=CloseArray[m]; StDev+=MathPow(CloseArray[m],2); } // проход по барам
StDev=StDev/(BarsPeriod-shift); // среднее от квадаратов Average=Summ/(BarsPeriod-shift);// простое среднее StDev=MathPow(StDev-MathPow(Average,2),0.5); // стандартное отклонение sigma Average=NormalizeDouble(Average,1); StDev=NormalizeDouble(StDev,1); MaxOnArray=NormalizeDouble(MaxOnArray,1); MinOnArray=NormalizeDouble(MinOnArray,1);
Stat[i_period][shift-1][0]=BarsPeriod-shift; Stat[i_period][shift-1][1]=Average; Stat[i_period][shift-1][2]=MaxOnArray; Stat[i_period][shift-1][3]=MinOnArray; Stat[i_period][shift-1][4]=StDev; begin=Average-3.0*StDev; for(t=0;t<13;t++) { range[t]=begin; begin+=0.5*StDev; if (GetLastError()==4002) Print("Имеем выход за пределы массива в блоке for (t=1;t<13;t++) range[t]"); } //if (GetLastError()==4002) Print("Имеем выход за пределы массива в блоке for (t=1;t<13;t++) range[t]"); rangeDescription[0]="меньше "+DoubleToStr(range[0],1); for (t=1;t<13;t++) { rangeDescription[t]="от "+DoubleToStr(range[t-1],1)+" до "+DoubleToStr(range[t],1); }
rangeDescription[13]="больше "+DoubleToStr(range[12],1); if (GetLastError()==4002) Print("Имеем выход за пределы массива в блоке for (t=1;t<13;t++) rangeDescription[t]");
ArrayInitialize(freequency,0); for(m=0;m<BarsPeriod-shift;m++) { if (CloseArray[m]<=range[0]) freequency[0]++; for (z=0;z<12;z++) { if ((range[z]<CloseArray[m])&&(CloseArray[m]<=range[z+1])) freequency[z+1]++; } if (range[12]<=CloseArray[m]) freequency[13]++; if (GetLastError()==4002) Print("Имеем выход за пределы массива в блоке for(m=0;m); } //Print("Тайм-фрейм ",curPeriod,"M cмещение ",shift," Среднее=",Average, //" СтОткл=",StDev," Max=",MaxOnArray," Min=",MinOnArray);
if (FileWrite(FileHandle,"Тайм-фрейм ",curPeriod,"M cмещение ",shift," Среднее=",Average, " СтОткл=",StDev," Max=",MaxOnArray," Min=",MinOnArray)<0) Print("Ошибка записи в файл",GetLastError());
for(z=0;z<14;z++) { //Print("в диапазоне ",rangeDescription[z]," найдено ",freequency[z]," значений"); FileWrite(FileHandle,z,rangeDescription[z],freequency[z]); if (GetLastError()==4002) Print("Имеем выход за пределы массива в блоке for(z=0;z<14;z++)"); } sigma=freequency[5]+freequency[6]+freequency[7]+freequency[8]; // число попаданий в интервал +-1 ст.отклонение sigma_2=sigma+freequency[3]+freequency[4]+freequency[9]+freequency[10]; // -/- в интервал +-2 ст.отклонений sigma_3=sigma_2+freequency[1]+freequency[2]+freequency[11]+freequency[12]; // -/- в интервал +-3 ст.отклонений sigma=NormalizeDouble(100*sigma/(BarsPeriod-shift),1); // процент попадания в интервал от -1 до +1 ст.откл. sigma_2=NormalizeDouble(100*sigma_2/(BarsPeriod-shift),1); // процент попадания в интервал от -2 до +2 sigma_3=NormalizeDouble(100*sigma_3/(BarsPeriod-shift),1); // процент попадания в интервал от -3 до +3
Stat[i_period][shift-1][5]=sigma; Stat[i_period][shift-1][6]=sigma_2; Stat[i_period][shift-1][7]=sigma_3;
FileWrite(FileHandle,sigma,"% в диапазоне стандартного отклонения "); FileWrite(FileHandle,sigma_2,"% в диапазоне 2-ух стандартных отклонений "); FileWrite(FileHandle,sigma_3,"% в диапазоне 3-ех стандартных отклонений "); FileWrite(FileHandle,"---"); }// проход по смещениям }// проход по таймфреймам //---- return(0); }
int PeriodNumber(int number) { int per_min; switch (number) { case 0: per_min=PERIOD_M1;break; case 1: per_min=PERIOD_M5;break; case 2: per_min=PERIOD_M15;break; case 3: per_min=PERIOD_M30;break; case 4: per_min=PERIOD_H1;break; case 5: per_min=PERIOD_H4;break; default: per_min=PERIOD_D1;break; } return(per_min); }

Я выделил блоки для лучшего понимания алгоритма.

 
 
 
 


Обратите внимание на многочисленное использование конструкции
 
 

Дело в том, что при обработке массивов динамических размеров или не двух массивов с разными размерами немудрено сделать ошибку, которая заключается в выходе за пределы массива. Например, я объявил массив
double Array[100]
и попытался обратиться к элементу Array[101](или Array[100]). В этом случае будет сгенерирована ошибка с кодом 4002. Прекращение работы программы она не вызовет, но код этой ошибки будет храниться до тех пор, пока не произойдет вызов GetLastError() (тогда значение внутренней переменной, содержащей код ошибки станет равным нулю) или пока не произойдет другая ошибка. У меня в скрипте была именно эта ошибка, и чтобы точно найти то место, где она происходит, мне пришлось обложить этой ловушкой все циклы. Другого надежного способа локализовать место ошибки нет.

После запуска скрипта будет создан файл ИмяСимволаCloseDist.csv . Открываем его, выделяем необходимые данные по частотам распределения и жмем кнопку «Мастера диаграмм».

 
 


Видно, что проверка подтверждает правильность работы скрипта – сумма выделенных ячеек равна количеству значений при заданном тайм-фрейме и смещении.

 
 


Выбираем тип диаграммы и жмем «Далее».

 
 


Нас все устраивает, жмем опять «Далее».

 
 


Если есть желание – заполняем остальные поля, и опять жмем «Далее». Последнее диалоговое окно.


 
 


Жмем готово и размещаем окно диаграммы поудобнее.

 
 


Теперь мы получили график распределения приращения для EURUSD M15. Видно, что через 10 баров с вероятностью 79% цена окажется не далее 23 пункта(1 сигма ) от текущей, с вероятностью 94.4% не далее 45 пунктов(2 сигмы) от текущей, и с вероятностью 98.2% не далее 67 пунктов(3 сигмы). Сделать вывод о том – соответствует ли данный график графику нормального распределения – вы должны сделать сами.

Скрипт правильный, но мы можем его немного доработать, используя технические индикаторы на массивах. Назовем новый скрипт CloseDist2.mq4 . Кроме того я ввел новую функцию – ArrayCopySeries(). Обратите внимание, что уже не требуется задавать размер массива temp, в который якобы копируются цены закрытия.

 
 


На самом деле с помощью этой функции мы получаем доступ к тайм-серии цен закрытия (MODE_CLOSE), хотя и обращаемся к массиву temp. При обращении к данным другого тайм-фрейма или символа возможна ситуация, когда отсутствует история котировок, в этом случае генерируется ошибка 4066. Во втором скрипте добавлена обработка ошибки 4006, но нужно обратить внимание, что этот пример не правильный:

 
 


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

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


Перейти к статье «Знакомство с тестером».
 
+7 (495) 710-76-76
8 (800) 200-01-31
по России бесплатно

закрыть

Вход в личный кабинет

Для счета alpari.classic введите номер счета (буква и 4 цифры) и пароль в ЛК.

Для счетов alpari.micro и alpari.partner введите логин и пароль в МТ.

Зарегистрироваться!Забыли пароль?

 
Rambler's Top100