최후의 성전
개요
MetaTrader 5 (MetaTrader4)에는 상품 가격을 나타내는 세 가지 방법이 기본으로 제공됩니다. 바, 캔들 그리고 선이죠. 세 가지 모두 시간 차트를 나타냅니다. 시간을 기준으로 하는 전통적인 가격 차트 외에 시간이 나타나지 않는 가격 차트 중에서도 꽤 인기를 끄는 차트들이 있습니다. 바로 Renko 차트와 Kagi 차트, 삼선전환도, 그리고 P&F 차트이죠.
고전적인 방법보다 더 나은 방법을 제시한다는 것은 아닙니다. 다만 시간 변수를 제외시키면 가격 변수에 집중하는 데에 보다 도움이 될 수 있죠. P&F 차트와 차트 알고리즘을 살펴보도록 할게요. 잘 알려진 시장 상품을 활용해 차트를 생성하고 알고리즘을 구현하는 깔끔하고 간단한 스크립트도 작성해 보겠습니다. 본문의 내용은 토마스 J 도시(Thomas J. Dorsey)의 저서 'Point and Figure Charting: The Essential Application for Forecasting and Tracking Market Prices'의 내용을 기준으로 합니다.
불스아이 브로커(Bull's-Eye Broker)는 가장 많이 이용되는 오프라인 차트 드로잉 소프트웨어 패키지입니다. 21일 동안 체험판을 이용할 수 있고 반복해서 체험할 수 있습니다. 베타 기간 동안에는 새로운 베타 버전이 제공됩니다. 해당 소프트웨어 패키지를 이용해 스크립트 실행 결과를 평가하도록 하겠습니다. P&F 차트에 가장 유용한 온라인 리소스 중 하나는 StockCharts입니다. 아쉽게도 주식시장 관련 웹사이트라서 외환시장 상품 가격은 제공하고 있지 않죠.
본문에서 소개할 스크립트 실행 결과를 비교하기 위해 불스아이 브로커와 스톡차트닷컴을 이용해 금 선물, 경질 원유 선물 및 S&P 500 CFD 차트를 생성하겠습니다. 추가적으로 불스아이 브로커만을 이용한 EURUSD 가격 차트 또한 생성합니다.
P&F 차트 알고리즘
알고리즘을 살펴보겠습니다.
P&F 차트에는 두 개의 주요 매개 변수가 있는데요.
- 우선 최소 가격 변동 값을 나타내는 '한칸값'이 있습니다. 해당 값보다 적은 가격 변동은 차트에 반영되지 않습니다.
- 그리고 '전환칸수'가 있죠. 현재의 추세의 반전을 나타내며 해당 반전폭 발생 시 새로운 열에 표시됩니다.
차트 작성에는 개장가-고가-저가-종가 형태로 저장된 시세 기록이 필요하죠.
예를 들어 볼게요. 박스값이 $1(하나)이고 전환칸수가 3(셋)인 경질 원유 차트를 그리려고 합니다. 고가와 저가는 $1 단위로 반내림되거나 반올림되어야 겠죠.
일자 | 고가 | 저가 | XO 고가 | XO 저가 |
---|---|---|---|---|
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 |
X는 가격 상승을, O는 가격 하락을 나타냅니다.
초기 가격 방향 결정 방법(첫 번째 열의 XO 값 구하기)
XO 고가와 XO 저가[Bars-1] 값을 잘 기억하고 다음 이벤트가 발생할 떄까지 기다리세요.
- 초기 XO 고가(첫 번째 열이 O인 경우)에 비해 전환칸수만큼 감소한 XO 저가 값 또는
- 초기 XO 저가(첫 번째 열이 X인 경우)에 비해 전환칸수만큼 증가한 XO 고가 값
해당 경질 원유 차트 예시의 경우 XO 고가와 XO 저가 모두 [Bars-1]=100입니다.
다음 중 어떤 이벤트가 먼저 발생하는지 확인하세요.
- 다음 바의 XO 저가[i] 값이 $97 이하인 경우, 즉 첫 번째 열이 O임을 시사하는 경우 또는
- 다음 바의 XO 고가[i] 값이 $103 이상인 경우, 즉 첫 번째 열이 X임을 시사하는 경우
2월 17일의 첫 번째 열을 알 수 있네요. XO 고가가 $103에 도달했으니 첫 번째 열은 X입니다. $100부터 $103 사이에 네 개의 X를 그립니다.
향후 차트 이동 방향 예측하기
현재 열이 X인 경우, 현재 바의 XO 고가가 현재 XO 가격에 비해 박스칸만큼 증가했는지 확인합니다. 2월 20일의 XO 고가가 $104 이상인지 확인하면 됩니다. 만약 2월 20일의 XO 고가가 $104, $105 혹은 그 이상인 경우 현재 X열에 해당 수만큼의 X를 추가합니다.
현재 바의 XO 고가가 현재 XO 가격에 비해 박스칸만큼 증가하지 않은 경우, 현재 바의 XO 저가가 XO 고가에서 전환칸수를 뺀 것보다 낮은지 확인합니다. 2월 20일의 XO 저가가 $103-3*$1 값인 $100 또는 $99이거나 그 미만인 경우입니다. 이 경우 X열 오른쪽으로 $102에서 $100까지 O열을 그립니다.
현재 열이 O인 경우 전부 반대 방향으로 생각하면 됩니다.
중요: 새로운 O열은 항상 이전 X열 고가 우측의 한칸값만큼 낮은 곳에 그려지며 새로운 X열은 항상 이전 O열 저가 우측의 한칸값만큼 높은 곳에 그려집니다.
원리는 잘 아시겠죠? 이번에는 지지선과 저항선에 대해 알아보겠습니다.
전통적인 P&F 차트에서 지지선과 저항선은 항상 45도 각도를 이룹니다.
첫 번째 선은 첫 번째 열과 관련이 있죠. 첫 번째 열의 값이 X인 경우 첫 번째 선은 첫 번째 열 최고가보다 한칸값만큼 높은 곳에서 시작해 오른쪽 하단을 향해 45도 각도로 그려지는 저항선이 됩니다. 첫 번째 열의 값이 O인 경우 첫 번째 선은 첫 번째 열 최저가보다 한칸값만큼 낮은 곳에서 시작해 오른쪽 상단을 향해 45도 각도로 그려지는 지지선이 됩니다. 지지선과 저항선은 가격 차트에 도달할 때까지 반복해서 그려집니다.
지지선과 저항선이 가격 차트에 도달하면 그 다음 가격 차트에 맞추어 다시 지지선과 저항선을 그리기 시작합니다. 가장 중요한 건 라인 차트가 이전 추세선보다 오른쪽에 위치하도록 하는 것입니다. 지지선을 그리려면 우선 저항선 아래의 최소 차트값을 알아야 합니다. 최소 차트값보다 한칸값만큼 낮은 곳에서 오른쪽 상단을 향하도록 지지선을 그리기 시작해 차트에 도달하거나 차트의 마지막 열에 도달할 때까지 반복합니다.
해당 지지선이 동일한 저항선에 걸리면 오른쪽으로 이동해 저항선 아래 최소 값부터 저항선 끝 사이에서 새로운 최소값을 찾습니다. 새로운 추세선이 기존 추세선을 넘어 오른쪽에 위치할 때까지 반복합니다.
실제 차트를 이용한 하단의 예시를 보면 좀 더 이해하기 쉬울 거예요.
알고리즘은 대충 이해가 됐을 겁니다. 이제 스크립트에 몇 가지 기능을 추가해 봅시다.
- 모드 선택: 현재 심볼/마켓워치 전체 심볼에 대한 차트 작성
- 타임프레임 선택(일간 타임프레임에는 100핍 차트를, M1에는 1~3핍 차트를 그리는 것이 논리적)
- 한칸값 설정(핍)
- 전환칸수 설정
- 행열 볼륨 글자수 설정(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" // Include the file containing cIntBMP class input bool mw=true; // All MarketWatch? input ENUM_TIMEFRAMES tf=PERIOD_M1; // Time frame input long box=2; // Box size in pips (0 - auto) enum cChoice{c10=10,c25=25,c50=50,c100=100}; input cChoice count=c50; // Chart height in boxes for autocharting enum rChoice{Two=2,Three,Four,Five,Six,Seven}; input rChoice reverse=Five; // Number of boxes for reversal enum vChoice{v10=10,v25=25,v50=50}; input vChoice vd=v10; // Characters for displaying volumes enum dChoice{Little=15000,Middle=50000,Many=100000,Extremely=1000000}; input dChoice depth=Little; // History depth input bool pic=true; // Image file? input int cellsize=10; // Cell size in pixels //+------------------------------------------------------------------+ //| cIntBMP class descendant | //+------------------------------------------------------------------+ 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 class instance uchar Mask_O[192]= // The naughts { 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]= // The crosses { 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 }; //+------------------------------------------------------------------+ //| Instrument selection | //+------------------------------------------------------------------+ 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."); } //+------------------------------------------------------------------+ //| Chart calculation and drawing | //+------------------------------------------------------------------+ void PNF(string sName, // instrument string& array[], // array for the output int& y, // array height int& z, // array width bool toPic, // if true-output and draw int cs) // set the cell size for drawing { 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; //--- shift for prices 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; }; }; }; //--- credits 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 image size on the chart display int XSize=cs*width+2*startX+RowVolWidth; int YSize=cs*height+yshift+70; //-- creating a bmp image sized XSize x YSize with the background color clrWhite bmp.Create(XSize,YSize,clrWhite); //-- displaying cells of the main field 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 ----------------------------------------- //--- calculating trend lines 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) { //--- support and resistance lines 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); } //--- broadening of support/resistance lines 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; }; //--- displaying data in columns 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; //--- displaying price chart 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; //--- simple markup through 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; //--- displaying volume chart in columns 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; //--- column history 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) { //--- displaying dates in YYYY/MM/DD format 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); } //--- volume cell support 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); } //--- exact volumes for(i=height-1;i>=0;i--) bmp.Bar(startX,yshift+cs*i,int(10*cs*VolByPrice[i]/HVolumeMax),cs,0xB5ABAB); //--- displaying naughts and crosses 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'); } //--- volume underside support 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); } //--- displaying volumes for(j=0;j<=width-1;j++) bmp.Bar(RowVolWidth+startX+cs*j,yshift-60, cs,int(60*VolByCol[j]/VVolumeMax),0xB5ABAB); //--- displaying the main field border bmp.Rectangle(RowVolWidth+startX+cs*0,yshift+cs*0,cs*(width),cs*(height),clrSilver); //--- displaying prices and scale 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--) { //-- prices on the left 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); //-- prices on the right 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; } //-- saving the resulting image in a file bmp.Save(sName,true); } // --------------------------------- BMP ----------------------------------------- } //+------------------------------------------------------------------+ //|Outputting as a text file | //+------------------------------------------------------------------+ void pnf2file(string sName, // instrument for the file name string& array[], // array of lines saved in the file int beg, // the line of the array first saved in the file int end) // the line of the array last saved in the file { 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); } //+------------------------------------------------------------------+ //| Adjusting the price to the box size | //+------------------------------------------------------------------+ int MathNorm(double value, // transforming any double-type figure into long-type figure double prec, // ensuring the necessary accuracy bool vect) // and if true, rounding up; if false, rounding down { if(vect==true) return((int)(MathCeil(value/prec))); else return((int)(MathFloor(value/prec))); } //+------------------------------------------------------------------+ //| Filling the array | //| Character one-dimensional array represented as a matrix | //+------------------------------------------------------------------+ void SetMatrix(uchar& array[], // passing the array in a link to effect a replacement long pbeg, // from here long pend, // up to here long pheight, // in the column of this height long pwidth, // bearing this number among all the columns in the array uchar ppnf) // with this character { long offset=0; for(offset=pheight*pwidth+pbeg;offset<=pheight*pwidth+pend;offset++) array[(int)offset]=ppnf; } //+------------------------------------------------------------------+ //| Getting an isolated value from the array | //| Character one-dimensional array represented as a matrix | //+------------------------------------------------------------------+ uchar GetMatrix(uchar& array[], // passing it in a link to obtain a character... long pbeg, // here long pheight, // in the column of this height long pwidth) // bearing this number among all the columns in the array { return(array[(int)pheight*(int)pwidth+(int)pbeg]); } //+------------------------------------------------------------------+ //|Filling the vector | //+------------------------------------------------------------------+ void SetVector(long &array[], // passing the long-type array in a link to effect a replacement long pbeg, // from here long pend, // up to here long pv) // with this value { long offset=0; for(offset=pbeg;offset<=pend;offset++) array[(int)offset]=array[(int)offset]+pv; } //+------------------------------------------------------------------+ //| Displaying a horizontal line | //+------------------------------------------------------------------+ void cIntBMPEx::LineH(int aX1,int aY1,int aSizeX,int aColor) { DrawLine(aX1,aY1,aX1+aSizeX,aY1,aColor); } //+------------------------------------------------------------------+ //| Displaying a vertical line | //+------------------------------------------------------------------+ void cIntBMPEx::LineV(int aX1,int aY1,int aSizeY,int aColor) { DrawLine(aX1,aY1,aX1,aY1+aSizeY,aColor); } //+------------------------------------------------------------------+ //| Drawing a rectangle (of a given size) | //+------------------------------------------------------------------+ void cIntBMPEx::Rectangle(int aX1,int aY1,int aSizeX,int aSizeY,int aColor) { DrawRectangle(aX1,aY1,aX1+aSizeX,aY1+aSizeY,aColor); } //+------------------------------------------------------------------+ //| Drawing a filled rectangle (of a given size) | //+------------------------------------------------------------------+ void cIntBMPEx::Bar(int aX1,int aY1,int aSizeX,int aSizeY,int aColor) { DrawBar(aX1,aY1,aX1+aSizeX,aY1+aSizeY,aColor); } //+------------------------------------------------------------------+ //| Drawing a filled rectangle | //+------------------------------------------------------------------+ 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); } } //+------------------------------------------------------------------+ //| Displaying the text vertically | //+------------------------------------------------------------------+ 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; } } } //+------------------------------------------------------------------+ //| Transforming components into color | //+------------------------------------------------------------------+ int RGB256(int aR,int aG,int aB) { return(aR+256*aG+65536*aB); } //+------------------------------------------------------------------+ //| Drawing X's or O's as an image | //+------------------------------------------------------------------+ 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 명령값에 따라 스크립트 실행 결과가 이미지가 포함된 텍스트 파일(terminal_data_directory\MQL5\Images) 또는 텍스트 전용 파일(terminal_data_directory\MQL5\Files)로 생성됩니다 .
결과 비교하기
한칸값이 $1이고 전환칸수가 3인 경질 원유 차트를 그려 결과를 비교하겠습니다.
StockCharts.com
그림 1. StockCharts.com에서 생성된 경질 원유 P&F 차트
불스아이 브로커(Bull's-Eye Broker)
그림 2. 불스아이 브로커 소프트웨어로 생성한 경질 원유 P&F 차트
다음은 스크립트 실행 결과입니다.
그림 3. 스크립트가 생성한 경질 원유 P&F 차트
세 차트가 모두 동일하네요. 잘하셨습니다! 이제 대충은 알겠죠?
기본 P&T 차트 패턴
차트 패턴을 어떻게 이용할까요?
우선 기본 패턴에 대해 알아보겠습니다. 몇 개 안됩니다.
다음의 세 가지가 있습니다.
그림 4. 가격 패턴: 이중 상단, 삼중 상단, 이중 하단 및 삼중 하단
다음 가격 패턴입니다.
그림 5. 가격 패턴: 상승 삼각형과 하락 삼각형
마지막 가격 패턴입니다.
그림 6. 가격 패턴: P&F 캐터펄트
이제 몇 가지 팁을 드릴게요.
- 지지선 위쪽으로는 롱 포지션을, 저항선 아래쪽으로는 숏 포지션만을 오픈하세요. 예를 들어, 2011년 9월부터 형성된 저항선이 돌파된 2011년 12월 중순 이후로는 경질 원유 선물에 대한 롱 포지션만을 오픈합니다.
- 지지선과 저항선을 추적 손절매에 이용하세요.
- 포지션 오픈 전에 예상 이익과 예상 손실을 계산해 보세요.
다음의 예를 통해 설명하겠습니다.
2011년 12월, 최초 가격이 $76였던 X열이 이전 X열의 $85를 넘어선 후 $87 저항선을 돌파하고 $89에 도달했습니다. 세로 카운트를 보면 가격이 $76+($89-$75)*3(전환칸수)의 값인 $118까지 올라갈 수 있을 것으로 보입니다.
실제 다음 가격 움직임은 조정을 통해 $85로 내려왔습니다. 투기 성향이 있으신 분들은 롱 포지션을 열어 $84에 손절을 설정하세요.
롱 포지션 진입 가격은 지난 X열보다 한칸값만큼 높은 지점에서 가격 조정이 완료된 후에 계획할 수 있습니다. 여기서는 $90가 되겠네요.
예상 손실을 추정해 봅니다. 선물 계약 당 $90에서 $84를 제외한 $6에 달할 수도 있습니다. 예상 이익은 $118에서 $90을 뺀 $28가 될 수도 있겠네요. 예상 이익:예상 손실 비율이 $28/$6>4.5이므로 제 생각에는 훌륭한 실적입니다. 지금쯤이면 모든 선물 계약마다 $105에서 $90을 뺀 $15 수익이 발생했겠네요.
라이선스
본문의 스크립트는 Roman Rich에 의해 개발되었으며 BSD라이선스 하에 배포됩니다. 라이선스 관련 사항은 Lic.txt 파일을 참조하세요. cIntBMP 라이브러리는 Integer(Dmitry)에 의해 개발되었습니다. StockCharts.com 및 불스아이 브로커(Bull's-Eye Broker)에 대한 상표권이 등록되어 있습니다.
결론
P&F 차트의 스크립트와 알고리즘을 살펴보았습니다. 본문에서 언급된 가격 패턴에 대한 설명은 링크를 참조하세요.
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/368