Последний крестовый поход
Введение
В терминале МetaТrader 5, (впрочем, как и в МetaТrader 4) по умолчанию доступны три способа отображения котировки инструмента: бары, японские свечи, линия. Все они, по сути, являются примерно одним и тем же: временными графиками. Наряду с традиционным способом временного отображения котировок до сих пор живы и пользуются известным успехом инвесторов и спекулянтов способы отображения котировок, не зависящие от времени: графики ренко, каги, трехлинейного прорыва и крестики-нолики.
Не берусь утверждать их преимущество перед классикой, но некоторым торговцам изъятие из поля зрения одной переменной - времени, помогает сосредоточиться на второй переменной - цене. Предлагаю рассмотреть в статье графики крестики-нолики, алгоритм их построения, известные рыночные продукты, предлагающие эти графики и написать самим простенький скрипт, реализующий алгоритм. Нашим букварем будет книга Томаса Дж. Дорси "Метод графического анализа крестики-нолики" (оригинал "Point and Figure Charting: The Essential Application for Forecasting and Tracking Market Prices").
Самым известным продуктом для построения оффлайн-графиков является программа Bull's-Eye Broker. Релиз программы предоставляется на ознакомление сроком на 21 день (знакомится можно много раз), новая бета-версия - до вывода ее в релиз. По релизу мы будем сверять результаты работы нашего скрипта. Одним из лучших онлайн-ресурсов, предоставляющем возможность построения крестиков-ноликов, является StockCharts. Сайт ориентирован на биржевую торговлю, поэтому найти на нем котировки по инструментам форекс, к сожалению, невозможно.
Для контроля результатов работы написанного нами скрипта построим аналогичные графики в программе и на сайте по инструментам: фьючерс на золото, фьючерс на нефть сорта Light crude oil, контракт на разницу S&P 500; только в Bull's-Eye Broker построим график EURUSD (помним об ограничениях StockChart).
Алгоритм построения графиков "крестики-нолики"
Итак, алгоритм.
Ключевыми понятиями при построении графиков крестики-нолики являются лишь два понятия:
- Размер бокса - минимальное изменение котировки инструмента, менее которого не происходит никаких изменений на графике;
- Разворот - количество боксов, проходимых котировкой в направлении, противоположном текущему направлению графика, после которого это движение будет отображено в новой колонке.
Поскольку для построения графика мы вынуждены использовать историю котировок, хранимой в виде цен Open-High-Low-Close, то принимаются следующие допущения:
- График строится по ценам High-Low;
- Цена High округляется до размера бокса вниз (MathFloor), цена Low округляется до размера бокса вверх (MathCeil).
Поясню на примере. Допустим, мы хотим строить график Light crude oil, размер бокса принимаем равным 1 (один) $ и разворот устанавливаем в 3 (три) бокса. Это значит, что все цены High мы округляем вниз с точностью 1$, а все цены Low - вверх с той же точностью:
Дата | High | Low | XO High | XO Low |
---|---|---|---|---|
2012.02.13 | 100.86 | 99.08 | 100 | 100 |
2012.02.14 | 101.83 | 100.28 | 101 | 101 |
2012.02.15 | 102.53 | 100.62 | 102 | 101 |
2012.02.16 | 102.68 | 100.84 | 102 | 101 |
2012.02.17 | 103.95 | 102.24 | 103 | 102 |
Движение цены вверх на графике отображается символом "Х" (крестик), движение вниз отображается символом "О" (нолик).
Как принимается решение о первоначальном направлении движения цены (первая колонка - это колонка Х или О):
Запоминаем значения XO High и XO Low бара Bars-1 и ждем, когда:
- цена XO Low станет на Разворотное число боксов меньше первоначальной XO High (первая колонка - колонка "О") или
- цена XO High станет на Разворотное число боксов больше первоначальной XO Low (первая колонка - колонка "Х").
В нашем примере с нефтью мы запомним цену XO High[Bars-1]=100 и XO Low[Bars-1]=100.
Далее ждем, что случится раньше:
- цена XO Low[i] очередного бара станет равна или меньше 97$ и у нас первая колонка будет колонкой "О", или
- цена XO High[i] очередного бара станет равна или больше 103$ и у нас первая колонка будет колонкой "Х".
17-го февраля мы получаем представление о первой колонке: цена XO High стала равна 103$ и наша первая колонка - это колонка "Х". Строим ее в виде четырех Х начиная с 100$ и завершая на 103$.
Как принимается решение о дальнейшем построении:
Если текущая колонка - колонка "Х", то проверяем: цена XO High текущего бара больше на Размер бокса текущей цены XO (т.е. 20-го февраля мы сначала будем проверять, цена XO High не стала равна или больше 104$). Если цена XO High[2012.02.20] будет 104$, или 105$, или еще больше, то мы в существующей колонке "Х" просто достраиваем сверху нужное количество "Х".
Если цена XO High текущего бара не больше на Размер бокса текущей цены XO, тогда проверяем, а цена XO Low текущего бара не меньше цены XO High на разворотное число боксов (в примере - цена XO Low[2012.02.20] равна или меньше 103$-3*1$=100$, или 99$, или еще ниже). Если меньше, то справа рядом, начиная со 102$ и продолжая до 100$, строим колонку "О".
При текущей колонке - колонке "О" все вышеприведенные соображения инвертируются.
ВАЖНО: новая колонка "О" всегда начинает строиться справа и на один бокс ниже верхнего значения предыдущей колонки "Х" и новая колонка "Х" всегда начинает строится справа и на один бокс выше нижнего значения предыдущей колонки "О".
С самим графиком разобрались. Теперь о линиях поддержки-сопротивления.
На ортодоксальных графиках крестиков-ноликов линии поддержки-сопротивления всегда идут под углом 45 градусов.
Построение первоначальной линии начинается в зависимости от значения первой колонки. Если первая колонка - колонка "Х", то первая линия будет линией сопротивления, и начнется она на один бокс выше максимума первой колонки, идти она будет под углом 45 градусов ВНИЗ и вправо. Если первая колонка - колонка "О", то первая линия будет линией поддержки и начнется она на один бокс ниже минимума первой колонки, идти она будет под углом 45 градусов ВВЕРХ и вправо. Линия поддержки-сопротивления строится до ее пересечения с графиком цены.
Как только линия поддержки/сопротивления пересекается с графиком цены, начинаем строить соответственно линию сопротивления/поддержки. Главным требованием при построении является то, чтобы построенная линия выходила на графике правее предыдущей трендовой линии. Таким образом, для построения линии поддержки мы выбираем сначала минимальное значение графика под только что завершенной линией сопротивления, строим линию поддержки от одного бокса ниже этого минимума ВВЕРХ вправо до пересечения с графиком, либо с последней колонкой графика.
Если линия от минимума под предыдущей линией сопротивления ушла и воткнулась в график по этой же линией сопротивления то сдвигаемся вправо, ищем новый минимум цены уже в диапазоне от минимального минимума под сопротивлением и окончанием линии сопротивления. Продолжаем, пока трендовая линия при построении не выйдет вправо за предыдущую трендовую линию.
Все вышесказанное проще всего будет представлено в дальнейшем на примере реальных графиков.
С алгоритмом построения самого графика к этому моменту мы с вами разобрались. Теперь прикрутим к нашему скрипту несколько приятных функций:
- Возможность выбрать режим: построение графика только для текущего символа, либо для всех символов в MarketWatch;
- Возможность выбора таймфрейма (графики в 100 pips логичнее строить на Daily, а графики в 1-3 pips на M1);
- Возможность выбора размера бокса в пипсах;
- Возможность выбора количества боксов для разворота;
- Возможность выбора количества символов для отображения объемов (в скрипте - тиковых, потому как реальных объемов пока у брокеров не видел) по колонкам и по строкам (типа индикатора MarketDepth);
- Возможность выбора глубины истории, на которой будем строить график;
- Возможность выбора режима построения результатов - вывод результатов в текстовые файлы, или также сохранять их в графические файлы;
- И, наконец, фича для новичков - автопостроение (автоматический выбор размера бокса исходя из желаемой высоты графика).
Алгоритм и требования описаны - скрипт в студию.
//+------------------------------------------------------------------+ //| Point&Figure text charts | //| BSD Lic. 2012, Roman Rich | //| http://www.FXRays.info | //+------------------------------------------------------------------+ #property copyright "Roman Rich" #property link "http://www.FXRays.info" #property version "1.00" #property script_show_inputs #include "cIntBMP.mqh" // Включаем файл с классом cIntBMP input bool mw=true; // Весь MarketWatch? input ENUM_TIMEFRAMES tf=PERIOD_M1; // Таймфрейм input long box=2; // Размер бокса в пипсах (0 - авто) enum cChoice{c10=10,c25=25,c50=50,c100=100}; input cChoice count=c50; // Высота графика в боксах для авто enum rChoice{Two=2,Three,Four,Five,Six,Seven}; input rChoice reverse=Five; // Кол-во боксов для разворота enum vChoice{v10=10,v25=25,v50=50}; input vChoice vd=v10; // Символов для отображения объемов enum dChoice{Little=15000,Middle=50000,Many=100000,Extremely=1000000}; input dChoice depth=Little; // Глубина истории input bool pic=true; // Файл рисунка? input int cellsize=10; // Размер ячейки в пикселах //+------------------------------------------------------------------+ //| Класс-наследник cIntBMP | //+------------------------------------------------------------------+ class cIntBMPEx : public cIntBMP { public: void Rectangle(int aX1,int aY1,int aSizeX,int aSizeY,int aColor); void Bar(int aX1,int aY1,int aSizeX,int aSizeY,int aColor); void LineH(int aX1,int aY1,int aSizeX,int aColor); void LineV(int aX1,int aY1,int aSizeY,int aColor); void DrawBar(int aX1,int aY1,int aX2,int aY2,int aColor); void TypeTextV(int aX,int aY,string aText,int aColor); }; cIntBMPEx bmp; // Экземпляр класса cIntBMPEx uchar Mask_O[192]= // Наши нолики { 217,210,241,111,87,201,124,102,206,165,150,221,237,234,248,255,255,255,255,255,255,255,255,255, 73,42,187,137,117,211,201,192,235,140,120,212,60,27,182,178,165,226,255,255,255,255,255,255, 40,3,174,250,249,253,255,255,255,255,255,255,229,225,245,83,54,190,152,135,216,255,255,255, 68,36,185,229,225,245,255,255,255,255,255,255,255,255,255,247,246,252,78,48,188,201,192,235, 140,120,212,145,126,214,255,255,255,255,255,255,255,255,255,255,255,255,188,177,230,124,102,206, 237,234,248,58,24,181,209,201,238,255,255,255,255,255,255,255,255,255,168,153,222,124,102,206, 255,255,255,199,189,234,63,30,183,186,174,229,247,246,252,204,195,236,60,27,182,204,195,236, 255,255,255,255,255,255,232,228,246,117,93,203,52,18,179,83,54,190,196,186,233,255,255,255 }; uchar Mask_X[192]= // Наши крестики { 254,252,252,189,51,51,236,195,195,255,255,255,255,255,255,235,192,192,248,234,234,255,255,255, 255,255,255,202,90,90,184,33,33,251,243,243,212,120,120,173,0,0,173,0,0,255,255,255, 255,255,255,254,252,252,195,69,69,192,60,60,178,15,15,233,186,186,253,249,249,255,255,255, 255,255,255,255,255,255,241,210,210,173,0,0,209,111,111,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,205,99,99,192,60,60,181,24,24,241,210,210,255,255,255,255,255,255, 255,255,255,249,237,237,176,9,9,241,213,213,226,165,165,189,51,51,254,252,252,255,255,255, 255,255,255,230,177,177,185,36,36,255,255,255,255,255,255,189,51,51,222,153,153,255,255,255, 255,255,255,240,207,207,200,84,84,255,255,255,255,255,255,227,168,168,211,117,117,255,255,255 }; //+------------------------------------------------------------------+ //| Выбор инструмента | //+------------------------------------------------------------------+ void OnStart() { int mwSymb; string symb; int height=0,width=0; string pnfArray[]; if(mw==true) { mwSymb=0; while(mwSymb<SymbolsTotal(true)) { symb=SymbolName(mwSymb,true); ArrayFree(pnfArray); ArrayResize(pnfArray,0,0); PNF(symb,pnfArray,height,width,pic,cellsize); pnf2file(symb,pnfArray,0,height); mwSymb++; }; } else { symb=Symbol(); ArrayFree(pnfArray); ArrayResize(pnfArray,0,0); PNF(symb,pnfArray,height,width,pic,cellsize); pnf2file(symb,pnfArray,0,height); }; Alert("Ok."); } //+------------------------------------------------------------------+ //| Непосредственно расчет и построение графика | //+------------------------------------------------------------------+ void PNF(string sName, // инструмент string& array[], // массив, куда направляем вывод int& y, // высота массива int& z, // ширина массива bool toPic, // если true-выведем и рисунок int cs) // примем размер ячейки для рисования { string s,ps; datetime d[]; double o[],h[],l[],c[]; long v[]; uchar matrix[]; long VolByPrice[],VolByCol[],HVolumeMax,VVolumeMax; int tMin[],tMax[]; datetime DateByCol[]; MqlDateTime bMDT,eMDT; string strDBC[]; uchar pnf='.'; int sd; int b,i,j,k=0,m=0; int GlobalMin,GlobalMax,StartMin,StartMax,CurMin,CurMax,RevMin,RevMax,ContMin,ContMax; int height,width,beg=0,end=0; double dBox,price; int thBeg=1,thEnd=2,tv=0; uchar trend='.'; // --------------------------------- BMP ----------------------------------------- int RowVolWidth=10*cs; //--- отступ для цен int startX=5*cs; int yshift=cs*7; // --------------------------------- BMP ----------------------------------------- if(SymbolInfoInteger(sName,SYMBOL_DIGITS)<=3) sd=2; else sd=4; b=MathMin(Bars(sName,tf),depth); ArrayFree(d); ArrayFree(o); ArrayFree(h); ArrayFree(l); ArrayFree(c); ArrayFree(v); ArrayFree(matrix); ArrayFree(VolByPrice); ArrayFree(VolByCol); ArrayFree(DateByCol); ArrayFree(tMin); ArrayFree(tMax); ArrayResize(d,b,0); ArrayResize(o,b,0); ArrayResize(h,b,0); ArrayResize(l,b,0); ArrayResize(c,b,0); ArrayResize(v,b,0); ArrayInitialize(d,NULL); ArrayInitialize(o,NULL); ArrayInitialize(h,NULL); ArrayInitialize(l,NULL); ArrayInitialize(c,NULL); ArrayInitialize(v,NULL); CopyTime(sName,tf,0,b,d); CopyOpen(sName,tf,0,b,o); CopyHigh(sName,tf,0,b,h); CopyLow(sName,tf,0,b,l); CopyClose(sName,tf,0,b,c); CopyTickVolume(sName,tf,0,b,v); if(box!=0) { dBox=box/MathPow(10.0,(double)sd); } else { dBox=MathNorm((h[ArrayMaximum(h,0,WHOLE_ARRAY)]-l[ArrayMinimum(l,0,WHOLE_ARRAY)])/count, 1/MathPow(10.0,(double)sd),true)/MathPow(10.0,(double)sd); }; GlobalMin=MathNorm(l[ArrayMinimum(l,0,WHOLE_ARRAY)],dBox,true)-(int)(reverse); GlobalMax=MathNorm(h[ArrayMaximum(h,0,WHOLE_ARRAY)],dBox,false)+(int)(reverse); StartMin=MathNorm(l[0],dBox,true); StartMax=MathNorm(h[0],dBox,false); ContMin=(int)(StartMin-1); ContMax=(int)(StartMax+1); RevMin=(int)(StartMax-reverse); RevMax=(int)(StartMin+reverse); height=(int)(GlobalMax-GlobalMin); width=1; ArrayResize(matrix,height*width,0); ArrayInitialize(matrix,'.'); ArrayResize(VolByPrice,height,0); ArrayInitialize(VolByPrice,0); ArrayResize(VolByCol,width,0); ArrayInitialize(VolByCol,0); ArrayResize(DateByCol,width,0); ArrayInitialize(DateByCol,D'01.01.1971'); ArrayResize(tMin,width,0); ArrayInitialize(tMin,0); ArrayResize(tMax,width,0); ArrayInitialize(tMax,0); for(i=1;i<b;i++) { CurMin=MathNorm(l[i],dBox,true); CurMax=MathNorm(h[i],dBox,false); switch(pnf) { case '.': { if(CurMax>=RevMax) { pnf='X'; ContMax=(int)(CurMax+1); RevMin=(int)(CurMax-reverse); beg=(int)(StartMin-GlobalMin-1); end=(int)(CurMax-GlobalMin-1); SetMatrix(matrix,beg,end,height,(int)(width-1),pnf); SetVector(VolByPrice,beg,end,v[i]); VolByCol[width-1]=VolByCol[width-1]+v[i]; DateByCol[width-1]=d[i]; trend='D'; break; }; if(CurMin<=RevMin) { pnf='O'; ContMin=(int)(CurMin-1); RevMax=(int)(CurMin+reverse); beg=(int)(CurMin-GlobalMin-1); end=(int)(StartMax-GlobalMin-1); SetMatrix(matrix,beg,end,height,(int)(width-1),pnf); SetVector(VolByPrice,beg,end,v[i]); VolByCol[width-1]=VolByCol[width-1]+v[i]; DateByCol[width-1]=d[i]; trend='U'; break; }; break; }; case 'X': { if(CurMax>=ContMax) { pnf='X'; ContMax=(int)(CurMax+1); RevMin=(int)(CurMax-reverse); end=(int)(CurMax-GlobalMin-1); SetMatrix(matrix,beg,end,height,(int)(width-1),pnf); SetVector(VolByPrice,beg,end,v[i]); VolByCol[width-1]=VolByCol[width-1]+v[i]; DateByCol[width-1]=d[i]; break; }; if(CurMin<=RevMin) { pnf='O'; ContMin=(int)(CurMin-1); RevMax=(int)(CurMin+reverse); tMin[width-1]=beg-1; tMax[width-1]=end+1; beg=(int)(CurMin-GlobalMin-1); end--; width++; ArrayResize(matrix,height*width,0); ArrayResize(VolByCol,width,0); ArrayResize(DateByCol,width,0); ArrayResize(tMin,width,0); ArrayResize(tMax,width,0); SetMatrix(matrix,0,(int)(height-1),height,(int)(width-1),'.'); SetMatrix(matrix,beg,end,height,(int)(width-1),pnf); SetVector(VolByPrice,beg,end,v[i]); VolByCol[width-1]=0; VolByCol[width-1]=VolByCol[width-1]+v[i]; DateByCol[width-1]=d[i]; tMin[width-1]=beg-1; tMax[width-1]=end+1; break; }; break; }; case 'O': { if(CurMin<=ContMin) { pnf='O'; ContMin=(int)(CurMin-1); RevMax=(int)(CurMin+reverse); beg=(int)(CurMin-GlobalMin-1); SetMatrix(matrix,beg,end,height,(int)(width-1),pnf); SetVector(VolByPrice,beg,end,v[i]); VolByCol[width-1]=VolByCol[width-1]+v[i]; DateByCol[width-1]=d[i]; break; }; if(CurMax>=RevMax) { pnf='X'; ContMax=(int)(CurMax+1); RevMin=(int)(CurMax-reverse); tMin[width-1]=beg-1; tMax[width-1]=end+1; beg++; end=(int)(CurMax-GlobalMin-1); width++; ArrayResize(matrix,height*width,0); ArrayResize(VolByCol,width,0); ArrayResize(DateByCol,width,0); ArrayResize(tMin,width,0); ArrayResize(tMax,width,0); SetMatrix(matrix,0,(int)(height-1),height,(int)(width-1),'.'); SetMatrix(matrix,beg,end,height,(int)(width-1),pnf); SetVector(VolByPrice,beg,end,v[i]); VolByCol[width-1]=0; VolByCol[width-1]=VolByCol[width-1]+v[i]; DateByCol[width-1]=d[i]; tMin[width-1]=beg-1; tMax[width-1]=end+1; break; }; break; }; }; }; //--- не забываем себя s="BSD License, 2012, FXRays.info by Roman Rich"; k++; ArrayResize(array,k,0); array[k-1]=s; s=SymbolInfoString(sName,SYMBOL_DESCRIPTION)+", Box-"+DoubleToString(box,0)+",Reverse-"+DoubleToString(reverse,0); k++; ArrayResize(array,k,0); array[k-1]=s; // --------------------------------- BMP ----------------------------------------- if(toPic==true) { //-- размеры изображения BMP на экране графика int XSize=cs*width+2*startX+RowVolWidth; int YSize=cs*height+yshift+70; //-- создаем bmp-картинку размером XSize x YSize c фоном цвета clrWhite bmp.Create(XSize,YSize,clrWhite); //-- отображение клеток основного поля for(i=height-1;i>=0;i--) for(j=0;j<=width-1;j++) { bmp.Bar(RowVolWidth+startX+cs*j,yshift+cs*i,cs,cs,clrWhite); bmp.Rectangle(RowVolWidth+startX+cs*j,yshift+cs*i,cs,cs,clrLightGray); } bmp.TypeText(10,yshift+cs*(height)+50,array[k-2],clrDarkGray); bmp.TypeText(10,yshift+cs*(height)+35,array[k-1],clrGray); } // --------------------------------- BMP ----------------------------------------- //--- рассчитываем линии тренда i=0; while(thEnd<width-1) { while(thBeg+i<thEnd) { if(trend=='U') { i=ArrayMinimum(tMin,thBeg,thEnd-thBeg); j=tMin[i]; } else { i=ArrayMaximum(tMax,thBeg,thEnd-thBeg); j=tMax[i]; } thBeg=i; tv=j; i=0; while(GetMatrix(matrix,j,height,(long)(thBeg+i))=='.') { i++; if(trend=='U') j++; else j--; if(thBeg+i==width-1) { thEnd=width-1; break; }; }; if(thBeg+i<thEnd) { thBeg=thBeg+2; i=0; }; }; thEnd=thBeg+i; if(thEnd==thBeg) thEnd++; for(i=thBeg;i<thEnd;i++) { SetMatrix(matrix,tv,tv,height,(long)(i),'+'); // --------------------------------- BMP ----------------------------------------- if(toPic==true) { //--- линии поддержки и сопротивления if(trend=='U') { bmp.DrawLine(RowVolWidth+startX+i*cs,yshift+tv*cs, RowVolWidth+startX+(i+1)*cs,yshift+(tv+1)*cs,clrGreen); } if(trend=='D') { bmp.DrawLine(RowVolWidth+startX+i*cs,yshift+(tv+1)*cs, RowVolWidth+startX+(i+1)*cs,yshift+(tv)*cs,clrRed); } //--- "уширение" линий поддержки/сопротивления if(trend=='U') { bmp.DrawLine(RowVolWidth+1+startX+i*cs,yshift+tv*cs, RowVolWidth+1+startX+(i+1)*cs,yshift+(tv+1)*cs,clrGreen); } if(trend=='D') { bmp.DrawLine(RowVolWidth+1+startX+i*cs,yshift+(tv+1)*cs, RowVolWidth+1+startX+(i+1)*cs,yshift+(tv)*cs,clrRed); } } // --------------------------------- BMP ----------------------------------------- if(trend=='U') tv++; else tv--; }; if(trend=='U') trend='D'; else trend='U'; i=0; }; //--- отображаем даты по столбцам ArrayResize(strDBC,width,0); TimeToStruct(DateByCol[0],bMDT); TimeToStruct(DateByCol[width-1],eMDT); if((DateByCol[width-1]-DateByCol[0])>=50000000) { for(i=0;i<=width-1;i++) StringInit(strDBC[i],4,' '); for(i=1;i<=width-1;i++) { TimeToStruct(DateByCol[i-1],bMDT); TimeToStruct(DateByCol[i],eMDT); if(bMDT.year!=eMDT.year) strDBC[i]=DoubleToString(eMDT.year,0); }; for(i=0;i<=3;i++) { StringInit(s,vd,' '); s=s+" : "; for(j=0;j<=width-1;j++) s=s+StringSubstr(strDBC[j],i,1); s=s+" : "; k++; ArrayResize(array,k,0); array[k-1]=s; }; } else { if((DateByCol[width-1]-DateByCol[0])>=5000000) { for(i=0;i<=width-1;i++) StringInit(strDBC[i],7,' '); for(i=1;i<=width-1;i++) { TimeToStruct(DateByCol[i-1],bMDT); TimeToStruct(DateByCol[i],eMDT); if(bMDT.mon!=eMDT.mon) { if(eMDT.mon<10) strDBC[i]=DoubleToString(eMDT.year,0)+".0"+DoubleToString(eMDT.mon,0); if(eMDT.mon>=10) strDBC[i]=DoubleToString(eMDT.year,0)+"."+DoubleToString(eMDT.mon,0); } }; for(i=0;i<=6;i++) { StringInit(s,vd,' '); s=s+" : "; for(j=0;j<=width-1;j++) s=s+StringSubstr(strDBC[j],i,1); s=s+" : "; k++; ArrayResize(array,k,0); array[k-1]=s; }; } else { for(i=0;i<=width-1;i++) StringInit(strDBC[i],10,' '); for(i=1;i<=width-1;i++) { TimeToStruct(DateByCol[i-1],bMDT); TimeToStruct(DateByCol[i],eMDT); if(bMDT.day!=eMDT.day) { if(eMDT.mon<10 && eMDT.day<10) strDBC[i]=DoubleToString(eMDT.year,0)+".0" +DoubleToString(eMDT.mon,0)+".0"+DoubleToString(eMDT.day,0); if(eMDT.mon<10 && eMDT.day>=10) strDBC[i]=DoubleToString(eMDT.year,0)+".0" +DoubleToString(eMDT.mon,0)+"."+DoubleToString(eMDT.day,0); if(eMDT.mon>=10&&eMDT.day< 10) strDBC[i]=DoubleToString(eMDT.year,0)+"." +DoubleToString(eMDT.mon,0)+".0"+DoubleToString(eMDT.day,0); if(eMDT.mon>=10&&eMDT.day>=10) strDBC[i]=DoubleToString(eMDT.year,0)+"." +DoubleToString(eMDT.mon,0)+"." +DoubleToString(eMDT.day,0); } }; for(i=0;i<=9;i++) { StringInit(s,vd,' '); s=s+" : "; for(j=0;j<=width-1;j++) s=s+StringSubstr(strDBC[j],i,1); s=s+" : "; k++; ArrayResize(array,k,0); array[k-1]=s; }; }; }; StringInit(s,25+vd+width,'-'); k++; ArrayResize(array,k,0); array[k-1]=s; //--- отображаем ценовой график price=GlobalMax*dBox; HVolumeMax=VolByPrice[ArrayMaximum(VolByPrice,0,WHOLE_ARRAY)]; s=""; for(i=height-1;i>=0;i--) { StringInit(ps,8-StringLen(DoubleToString(price,sd)),' '); s=s+ps+DoubleToString(price,sd)+" : "; for(j=0;j<vd;j++) if(VolByPrice[i]>HVolumeMax*j/vd) s=s+"*"; else s=s+" "; s=s+" : "; for(j=0;j<=width-1;j++) s=s+CharToString(matrix[j*height+i]); s=s+" : "+ps+DoubleToString(price,sd); k++; ArrayResize(array,k,0); array[k-1]=s; s=""; price=price-dBox; }; StringInit(s,25+vd+width,'-'); k++; ArrayResize(array,k,0); array[k-1]=s; //--- простенькая разметка через 10-ку StringInit(s,vd,' '); s=s+" : "; for(j=0;j<=width-1;j++) if(StringGetCharacter(DoubleToString(j,0), StringLen(DoubleToString(j,0))-1)==57) s=s+"|"; else s=s+" "; s=s+" : "; k++; ArrayResize(array,k,0); array[k-1]=s; //--- отображаем график объемов по столбцам VVolumeMax=VolByCol[ArrayMaximum(VolByCol,0,WHOLE_ARRAY)]; for(i=vd-1;i>=0;i--) { StringInit(s,vd,' '); s=s+" : "; for(j=0;j<=width-1;j++) if(VolByCol[j]>VVolumeMax*i/vd) s=s+"*"; else s=s+" "; s=s+" : "; k++; ArrayResize(array,k,0); array[k-1]=s; }; StringInit(s,25+vd+width,'-'); k++; ArrayResize(array,k,0); array[k-1]=s; //--- история колонок s=" | Start Date/Time | End Date/Time | "; k++; ArrayResize(array,k,0); array[k-1]=s; TimeToStruct(DateByCol[0],bMDT); s=" 1 | 0000/00/00 00:00:00 | "; s=s+DoubleToString(bMDT.year,0)+"/"; if(bMDT.mon >=10) s=s+DoubleToString(bMDT.mon ,0)+"/"; else s=s+"0"+DoubleToString(bMDT.mon ,0)+"/"; if(bMDT.day >=10) s=s+DoubleToString(bMDT.day ,0)+" "; else s=s+"0"+DoubleToString(bMDT.day ,0)+" "; if(bMDT.hour>=10) s=s+DoubleToString(bMDT.hour,0)+":"; else s=s+"0"+DoubleToString(bMDT.hour,0)+":"; if(bMDT.min >=10) s=s+DoubleToString(bMDT.min ,0)+":"; else s=s+"0"+DoubleToString(bMDT.min ,0)+":"; if(bMDT.sec >=10) s=s+DoubleToString(bMDT.sec ,0)+" | "; else s=s+"0"+DoubleToString(bMDT.sec ,0)+" | "; k++; ArrayResize(array,k,0); array[k-1]=s; for(i=1;i<=width-1;i++) { TimeToStruct(DateByCol[i-1],bMDT); TimeToStruct(DateByCol[i],eMDT); s=""; StringInit(ps,4-StringLen(DoubleToString(i+1,0)),' '); s=s+ps+DoubleToString(i+1,0)+" | "; s=s+DoubleToString(bMDT.year,0)+"/"; if(bMDT.mon >=10) s=s+DoubleToString(bMDT.mon ,0)+"/"; else s=s+"0"+DoubleToString(bMDT.mon ,0)+"/"; if(bMDT.day >=10) s=s+DoubleToString(bMDT.day ,0)+" "; else s=s+"0"+DoubleToString(bMDT.day ,0)+" "; if(bMDT.hour>=10) s=s+DoubleToString(bMDT.hour,0)+":"; else s=s+"0"+DoubleToString(bMDT.hour,0)+":"; if(bMDT.min >=10) s=s+DoubleToString(bMDT.min ,0)+":"; else s=s+"0"+DoubleToString(bMDT.min ,0)+":"; if(bMDT.sec >=10) s=s+DoubleToString(bMDT.sec ,0)+" | "; else s=s+"0"+DoubleToString(bMDT.sec ,0)+" | "; s=s+DoubleToString(eMDT.year,0)+"/"; if(eMDT.mon >=10) s=s+DoubleToString(eMDT.mon ,0)+"/"; else s=s+"0"+DoubleToString(eMDT.mon ,0)+"/"; if(eMDT.day >=10) s=s+DoubleToString(eMDT.day ,0)+" "; else s=s+"0"+DoubleToString(eMDT.day ,0)+" "; if(eMDT.hour>=10) s=s+DoubleToString(eMDT.hour,0)+":"; else s=s+"0"+DoubleToString(eMDT.hour,0)+":"; if(eMDT.min >=10) s=s+DoubleToString(eMDT.min ,0)+":"; else s=s+"0"+DoubleToString(eMDT.min ,0)+":"; if(eMDT.sec >=10) s=s+DoubleToString(eMDT.sec ,0)+" | "; else s=s+"0"+DoubleToString(eMDT.sec ,0)+" | "; k++; ArrayResize(array,k,0); array[k-1]=s; }; y=k; z=25+vd+width; // --------------------------------- BMP ----------------------------------------- if(toPic==true) { //--- отображение дат в виде YYYY/MM/DD for(j=0;j<=width-1;j++) { string s0=strDBC[j]; StringReplace(s0,".","/"); bmp.TypeTextV(RowVolWidth+startX+cs*j,yshift+cs*(height-1)+5,s0,clrDimGray); } //--- подложка с клетками для объемов for(i=height-1;i>=0;i--) for(j=0;j<vd;j++) { bmp.Bar(cs+startX+cs*(j-1),yshift+cs*i,cs,cs,0xF6F6F6); bmp.Rectangle(cs+startX+cs*(j-1),yshift+cs*i,cs,cs,clrLightGray); } for(i=0; i>-7;i--) for(j=0;j<=vd;j++) { bmp.Bar(cs+startX+cs*(j-1),yshift+cs*i,cs,cs,clrWhite); bmp.Rectangle(cs+startX+cs*(j-1),yshift+cs*i,cs,cs,clrLightGray); } //--- объемы точные for(i=height-1;i>=0;i--) bmp.Bar(startX,yshift+cs*i,int(10*cs*VolByPrice[i]/HVolumeMax),cs,0xB5ABAB); //--- отображение крестиков и ноликов for(i=height-1;i>=0;i--) for(j=0;j<=width-1;j++) { int xpos=RowVolWidth+startX+cs*j+1; int ypos=yshift+cs*i+1; if(CharToString(matrix[j*height+i])=="X") ShowCell(xpos,ypos,'X'); else if(CharToString(matrix[j*height+i])=="O") ShowCell(xpos,ypos,'O'); } //--- подложка для объемов внизу for(i=0;i<=60/cs;i++) for(j=0;j<=width-1;j++) { bmp.Bar(RowVolWidth+startX+cs*j,12+cs*i,cs,cs,0xF6F6F6); bmp.Rectangle(RowVolWidth+startX+cs*j,12+cs*i,cs,cs,clrLightGray); } //--- отображение объемов for(j=0;j<=width-1;j++) bmp.Bar(RowVolWidth+startX+cs*j,yshift-60, cs,int(60*VolByCol[j]/VVolumeMax),0xB5ABAB); //--- отображение рамки основного поля bmp.Rectangle(RowVolWidth+startX+cs*0,yshift+cs*0,cs*(width),cs*(height),clrSilver); //--- отображение цен и шкалы bmp.LineV(startX,yshift,cs*height,clrBlack); bmp.LineV(RowVolWidth+startX+cs*width,yshift,cs*height,clrBlack); price=GlobalMax*dBox; for(i=height-1;i>=0;i--) { //-- цены слева bmp.TypeText(cs,yshift+cs*i,DoubleToString(price,sd),clrBlack); bmp.LineH(0,yshift+cs*i,startX,clrLightGray); bmp.LineH(0+startX-3,yshift+cs*i,6,clrBlack); //-- цены справа int dx=RowVolWidth+cs*width; bmp.TypeText(10+startX+dx,yshift+cs*i,DoubleToString(price,sd),clrBlack); bmp.LineH(startX+dx,yshift+cs*i,40,clrLightGray); bmp.LineH(startX+dx-3,yshift+cs*i,6,clrBlack); price=price-dBox; } //-- сохраняем полученную картинку в файл bmp.Save(sName,true); } // --------------------------------- BMP ----------------------------------------- } //+------------------------------------------------------------------+ //|Выводим в текстовый файл | //+------------------------------------------------------------------+ void pnf2file(string sName, // инструмент для имени файла string& array[], // массив строк, который записываем в файл int beg, // с какой строки массива начинаем записывать в файл int end) // и какой строкой заканчиваем { string fn; int handle; fn=sName+"_b"+DoubleToString(box,0)+"_r"+DoubleToString(reverse,0)+".txt"; handle=FileOpen(fn,FILE_WRITE|FILE_TXT|FILE_ANSI,';'); for(int i=beg;i<end;i++) FileWrite(handle,array[i]); FileClose(handle); } //+------------------------------------------------------------------+ //| Приводим цену к размеру бокса | //+------------------------------------------------------------------+ int MathNorm(double value, // любое double-число преобразуем в long-число double prec, // доводим его до точности bool vect) // и если true - округляем вверх, false - округляем вниз { if(vect==true) return((int)(MathCeil(value/prec))); else return((int)(MathFloor(value/prec))); } //+------------------------------------------------------------------+ //| Заполняем массив | //| Символьный одномерный массив представляем в виде матрицы | //+------------------------------------------------------------------+ void SetMatrix(uchar& array[], // передаем его по ссылке для замены в нем long pbeg, // начиная с этого места long pend, // по это место long pheight, // в колонке такой высоты long pwidth, // с таким номером из числа всех колонок массива uchar ppnf) // вот этим символом { long offset=0; for(offset=pheight*pwidth+pbeg;offset<=pheight*pwidth+pend;offset++) array[(int)offset]=ppnf; } //+------------------------------------------------------------------+ //| Получаем из массива единичное значение | //| Символьный одномерный массив представляем в виде матрицы | //+------------------------------------------------------------------+ uchar GetMatrix(uchar& array[], // передаем его по ссылке для получения из него символа... long pbeg, // в этом месте long pheight, // в колонке такой высоты long pwidth) // с таким номером из числа всех колонок массива { return(array[(int)pheight*(int)pwidth+(int)pbeg]); } //+------------------------------------------------------------------+ //|Заполняем вектор | //+------------------------------------------------------------------+ void SetVector(long &array[], // long-массив передаем по ссылке для замены в нем long pbeg, // начиная с этого места long pend, // по это место long pv) // вот этим значением { long offset=0; for(offset=pbeg;offset<=pend;offset++) array[(int)offset]=array[(int)offset]+pv; } //+------------------------------------------------------------------+ //| Отображение горизонтальной линии | //+------------------------------------------------------------------+ void cIntBMPEx::LineH(int aX1,int aY1,int aSizeX,int aColor) { DrawLine(aX1,aY1,aX1+aSizeX,aY1,aColor); } //+------------------------------------------------------------------+ //| Отображение вертикальной линии | //+------------------------------------------------------------------+ void cIntBMPEx::LineV(int aX1,int aY1,int aSizeY,int aColor) { DrawLine(aX1,aY1,aX1,aY1+aSizeY,aColor); } //+------------------------------------------------------------------+ //| Рисует прямоугольник (заданного размера) | //+------------------------------------------------------------------+ void cIntBMPEx::Rectangle(int aX1,int aY1,int aSizeX,int aSizeY,int aColor) { DrawRectangle(aX1,aY1,aX1+aSizeX,aY1+aSizeY,aColor); } //+------------------------------------------------------------------+ //| Рисует закрашенный прямоугольник (заданного размера) | //+------------------------------------------------------------------+ void cIntBMPEx::Bar(int aX1,int aY1,int aSizeX,int aSizeY,int aColor) { DrawBar(aX1,aY1,aX1+aSizeX,aY1+aSizeY,aColor); } //+------------------------------------------------------------------+ //| Рисует закрашенный прямоугольник | //+------------------------------------------------------------------+ void cIntBMPEx::DrawBar(int aX1,int aY1,int aX2,int aY2,int aColor) { for(int i=aX1; i<=aX2; i++) for(int j=aY1; j<=aY2; j++) { DrawDot(i,j,aColor); } } //+------------------------------------------------------------------+ //| Вертикальное отображение текста | //+------------------------------------------------------------------+ void cIntBMPEx::TypeTextV(int aX,int aY,string aText,int aColor) { SetDrawWidth(1); for(int j=0;j<StringLen(aText);j++) { string TypeChar=StringSubstr(aText,j,1); if(TypeChar==" ") { aY+=5; } else { int Pointer=0; for(int i=0;i<ArraySize(CA);i++) { if(CA[i]==TypeChar) { Pointer=i; } } for(int i=PA[Pointer];i<PA[Pointer+1];i++) { DrawDot(aX+YA[i],aY+MaxHeight+XA[i],aColor); } aY+=WA[Pointer]+1; } } } //+------------------------------------------------------------------+ //| Преобразует компоненты в цвет | //+------------------------------------------------------------------+ int RGB256(int aR,int aG,int aB) { return(aR+256*aG+65536*aB); } //+------------------------------------------------------------------+ //| Рисует "X" или "0" в виде картинки | //+------------------------------------------------------------------+ void ShowCell(int x,int y,uchar img) { uchar r,g,b; for(int i=0; i<8; i++) { for(int j=0; j<8; j++) { switch(img) { case 'X': r=Mask_X[3*(j*8+i)]; g=Mask_X[3*(j*8+i)+1]; b=Mask_X[3*(j*8+i)+2]; break; case 'O': r=Mask_O[3*(j*8+i)]; g=Mask_O[3*(j*8+i)+1]; b=Mask_O[3*(j*8+i)+2]; break; }; int col=RGB256(r,g,b); bmp.DrawDot(x+i,y+j,col); } } } //+------------------------------------------------------------------+
В зависимости от значения входного параметра pic результатом работы скрипта будут текстовые файлы с файлами картинок (каталог_данных_терминала\MQL5\Images) или только текстовые файлы (сохраняются в папке: каталог_данных_терминала\MQL5\Files).
Сравнение результатов
Для сравнения результатов построим график Light Crude Oil с параметрами: бокс - 1$, разворот - 3 бокса.
StockCharts.com:
Рис. 1. Результат построения графика "крестики-нолики" для Light Crude Oil программой StockCharts.com
Bull's-Eye Broker:
Рис. 2. Результат построения графика "крестики-нолики" для Light Crude Oil программой Bull's-Eye Broker
Результат работы нашего скрипта:
Рис. 3. Результат построения графика "крестики-нолики" для Light Crude Oil при помощи нашего скрипта
Все три графика идентичны, поздравим себя, мы освоили построение крестиков-ноликов.
Типичные паттерны графиков "крестики-нолики"
Как все это можно использовать?
Сначала рассмотрим типичные паттерны, благо, в крестиках-ноликах их все можно перечесть на пальцах.
Вот они:
Рис. 4. Ценовые паттерны "Двойная вершина", "Тройная вершина", "Двойное основание" и "Тройное основание"
далее:
Рис. 5. Ценовые паттерны "Бычий треугольник" и "Медвежий треугольник"
и наконец:
Рис. 6. Ценовые паттерны "Бычья катапульта" и "Медвежья катапульта"
Теперь несколько советов.
- Открываться только вверх, находясь над линией поддержки и только вниз, находясь под линией сопротивления. Например, начиная с середины декабря 2011 года после пробития ценой линии сопротивления, идущей с конца сентября 2011 года, открывать только длинные позиции по нефтяному фьючерсу.
- Линии поддержки-сопротивления использовать для трейлинга приказов стоп-лосс.
- Перед открытием позиции применить правило вертикального счета для оценки соотношения возможного профита к возможным потерям.
Остановлюсь подробнее на правиле вертикального счета на примере.
В декабре 2011 года колонка "Х", начавшаяся от цены 76$, пошла вверх, превысила предыдущую колонку "Х" на уровне 85$, пробила на 87$ сопротивление и дошла до 89$. Правило вертикального счета гласит, что при таких раскладах цена может пройти вверх до цены, равной 76$+(89$-75$)*3 (разворот 3 бокса)=118$.
Следующее ценовое движение было коррекционным до цены 85$. Спекулянты могут выставить защитный стоп по длинной сделке на 1$ ниже, т.е. 84$.
Вход в длинную позицию можно планировать после окончания коррекционного движения на один бокс выше предыдущей колонки "Х", т.е. по цене 90$.
Оценим возможные потери, они могут составить 90$-84$=6$ на один фьючерс. Возможная прибыль может составить 118$-90$=28$. Соотношение возможной прибыли к возможному убытку 28$/6$>4.5 раз. По-моему, удачный бизнес. Уже сейчас наша прибыль составляла бы 105$-90$=15$ на каждый фьючерс.
Лиценезии
Скрипт написан и распространяется на условиях BSD автором Roman Rich. Текст лицензии в файле Lic.txt. Библиотека cIntBMP написана Дмитрием aka Integer. Торговые марки StockCharts.com и Bull's-Eye Broker принадлежат соответствующим правообладателям.
Заключение
В статье предложен алгоритм построения пункто-цифровых графиков ("крестики-нолики") и скрипт, позволяющий производить построение данных графиков. Рассмотрены типичные ценовые паттерны графиков "крестики-нолики", даны рекомендации по их практическому использованию.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Возможно проблема связана с тем, что не загружена история. Ее можно загрузить автоматически по всем символам при помощи CDownLoadHistory
загрузил историю скриптом downloadhistoryvisualmode.mq5 с настройкой: Вариант загрузки истории = Все символы из обзора рынка
после исполнения скрипта перезагрузил терминал, затем еще раз запустил скрипт downloadhistoryvisualmode и следом запустил PnF.mq5 с настройками по умолчанию
в журнале:
2012.03.14 19:38:00 Scripts script PnF (EURUSD,H1) removed
2012.03.14 19:38:00 MemoryException 1048576 bytes not available
2012.03.14 19:27:36 Scripts script PnF (EURUSD,H1) loaded successfully
2012.03.14 19:27:28 Scripts script downloadhistoryvisualmode (EURUSD,H1) removed
2012.03.14 19:26:41 Scripts script downloadhistoryvisualmode (EURUSD,H1) loaded successfully
во вкладке эксперты:
2012.03.14 19:38:00 PnF (EURUSD,H1) array out of range in 'cIntBMP.mqh' (348,21)
2012.03.14 19:27:28 downloadhistoryvisualmode (EURUSD,H1) Загрузка выполнена успешно
запустил затем с настройками в соответсвии с Вашим скрином, в журнале:
2012.03.14 19:56:11 Scripts script PnF (EURUSD,H1) removed
2012.03.14 19:55:57 Scripts script PnF (EURUSD,H1) loaded successfully
во вкладке эксперты:
2012.03.14 19:56:11 PnF (EURUSD,H1) Ok.
в папке МТ5:
файлы с рисунками содержат изображения графиков ХО, но терминал ничего не нарисовал,
нашел в чем была проблема: у меня файл подкачки Win задан max/min = 2048/2048 при 2Га оперативки никогда ни на одном приложении (MATLAB, Statistica, MSOffice, Delphi, debuggerы...,включая игры ) до сих пор ни разу не испытывал проблем с нехваткой памяти. Выставил файл подкачки "авто", скрипт в журнале:
2012.03.14 20:13:02 Scripts script PnF (EURUSD,H1) removed
2012.03.14 20:12:40 Scripts script PnF (EURUSD,H1) loaded successfully
во вкладке эксперты:2012.03.14 20:13:02 PnF (EURUSD,H1) Ok.
хм..., даже комментировать нет желания, что я думаю по этому поводу....
насколько я понял скрипт лишь формирует картинки в папке МТ5? если да, то на кой они нужны? я могу с разных ресурсов в предлагаемых web-платформах делать аналогичные скрины, не говоря о терминалах которые предоставляют ТФ в виде ХО
ЗЫ: неужели разработчикам настолько затруднительно сделать возможность средствами МТ5 рисовать по канве окна ТФ? про пользовательские оффлайн графики просто промолчу.....
удачи!
...
ЗЫ: неужели разработчикам настолько затруднительно сделать возможность средствами МТ5 рисовать по канве окна ТФ? про пользовательские оффлайн графики просто промолчу.....
удачи!
Вроде бы Ренат, что-то такое объявлял уже на днях, если не ошибаюсь.
Уже в работе.
Сначала сделаем легкий способ динамического создания битмапов и привязки их к графическим объектам, а уже затем штатные функции рисования к битмапе. Хотя уже и без штатных функций при желании можно будет самому рисовать в буфере.