キャンバスベースのインジケーター:チャネル内を透明にする
はじめに
この記事では、標準ライブラリのCCanvasクラスを使用して描画されるカスタムインジケーターを作成する方法を紹介します。特に、2本の線の間の領域を単色で塗りつぶす必要があるインジケーターに取り組みます。開始する前に、この種のインジケーターで現在利用可能なオプションよりもキャンバスを使用することが最良の選択である理由を確認します。その後、座標を計算するために必要ないくつかのチャートプロパティと、CCanvasの操作を含む基本的なプロセスについて説明します。
最終的な目標は、これまでに見たすべてを使用して、透明性を適用したインジケーターを構築することです。すべての作業では、メインチャートウィンドウのみを考慮します。目標が達成されたら、それをサブウィンドウインジケーターでの作業に拡張できます。
この記事のトピックは次のとおりです。
- キャンバスを使用する理由
- チャートウィンドウのプロパティ
- チャートウィンドウのプロパティについて
- チャートプロパティビューアインジケーター
- 座標変換
- 透明なDRAW_FILLING
- サブウィンドウインジケーターで機能するようにメソッドを拡張する
キャンバスを使用する理由
カスタムインジケーターで既に利用可能なDRAW_FILLINGの代わりにキャンバスを使用するのには、少なくとも2つの理由があります。
- インジケーターの色が他のインジケーター、ローソク足、チャートオブジェクトの色と混ざっている
- DRAW_FILLINGでは透明を使用できない
チャートウィンドウのプロパティ
カスタムチャートの描画を開始するには、いくつかのチャートプロパティについて考える必要があります。すべてのプロパティはドキュメントにあります。これらのプロパティ値を取得するには、適切なChartGetInteger関数とChartGetDouble関数を使用する必要があります。ChartGetStringもありますが、ここでは扱いません。
次は、使用するプロパティと簡単な説明です。さらに必要な場合は、後で指摘します。
- CHART_WIDTH_IN_PIXELS:価格スケールを含まないチャートウィンドウの幅
- CHART_HEIGTH_IN_PIXELS:日付スケールを含まないサブウィンドウの高さ
- CHART_PRICE_MIN:サブウィンドウの上部に対応する価格
- CHART_PRICE_MAX:サブウィンドウの下部に対応する価格
- CHART_SCALE:バー間の間隔(いくらかテストした後、値がpow(2,CHART_SCALE)によって与えられる2のべき乗であることを発見しました)
- CHART_FISRT_VISIBLE_BAR:チャートの最初に表示されるバー(左から右)
- CHART_VISIBLE_BARS:チャートに表示されるバーの量
チャートウィンドウのプロパティについて
これらのプロパティは、次の図で簡単に確認できます。
使用するCHART_WIDTH_IN_PIXELSプロパティとCHART_HEIGTH_IN_PIXELSプロパティは、描画のために作成する必要があるキャンバスオブジェクトのサイズを決定します。チャートウィンドウが変更されてこれらのプロパティが変更された場合は、キャンバスサイズを調整する必要があります。
理解を深めるために、プロパティと価格の変化とユーザーの操作に基づいてプロパティがどのように変化するかを示す簡単なインジケーターを作成します。キャンバスの描画プロセスを理解するために、既にキャンバスの使用を開始します。
チャートプロパティビューアインジケーター
この時点で、読者がカスタムインジケーターの作成方法を既にご存じであるることを前提としていますが、ご存じでない場合は、まず、「MQL5:自分のインジケーターの作成」稿と「多色ローソク足を作成するためのオプションの探究」稿をご覧ください。始めましょう。
このパスにインジケーターを作成しました。整理目的で同じにすることをお勧めします。
インジケーターの骨組みの準備ができたら、CCanvasライブラリをファイルに追加します。それには、#includeディレクティブを使用します。
次に、CCanvasクラスのインスタンスを作成します。これらはすべて、インジケーターの#propertyディレクティブの直後にあります。
#property copyright "Copyright 2023, Samuel Manoel De Souza" #property link "https://www.mql5.com/ja/users/samuelmnl" #property version "1.00" #property indicator_chart_window #include <Canvas/Canvas.mqh> CCanvas Canvas;
CCanvasを使用する際にまずしなければいけないのは、OBJ_BITMAP_LABELを作成し、それにリソースをリンクすることです。これはチャートに追加したい場合に必要です。通常はインジケーターの初期化でCreateBitampLabel(...)メソッドを使用します。最後に、OBJ_BITMAP_LABELとそれに関連付けられているリソースを削除します。チャートから削除したい場合、通常はインジケーターの初期化解除で、Destory(void)メソッドを使用します。それまでの間、基本的な描画プロセスを実行します。これは、描画の消去(リソースのピクセル値をクリアするまたはデフォルトのピクセル値を設定する)、描画の作成、およびリソースの更新で構成されます。キャンバスプロセスの完全なライフサイクルは、次の図のようになります。
簡単にするために、Erase、Draw、UpdateをRedrawという単一の関数に保持します。コードにすべてを書くと、次の構造が得られます。
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping Canvas.CreateBitmapLabel(0, 0, "Canvas", 0, 0, 200, 150, COLOR_FORMAT_ARGB_NORMALIZE); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Canvas.Destroy(); } //+------------------------------------------------------------------+ //| Custom indicator redraw function | //+------------------------------------------------------------------+ void Redraw(void) { uint default_color = ColorToARGB(clrBlack); uint text_color = ColorToARGB(clrWhite); //--- canvas erase Canvas.Erase(default_color); //--- add first draw //--- add second draw //--- add ... draw //--- add last draw //--- canvas update Canvas.Update(); }
プロパティを表示するには、TextOutメソッドを使用して書き込みます。これらのプロパティ値は、structarray変数に文字列として格納されます。
struct StrProperty { string name; string value; };構造体は次のようになります。次に、それらをループで簡単に出力できます。まだ配列がないので、配列をRedraw関数のパラメータとして渡します。Redraw関数は次のようになります。
void Redraw(StrProperty &array[]) { uint default_color = ColorToARGB(clrBlack); uint text_color = ColorToARGB(clrWhite); //--- canvas erase Canvas.Erase(default_color); //--- add first draw int total = ArraySize(array); for(int i=0;i<total;i++) { int padding = 2; int left = padding, right = Canvas.Width() - padding, y = i * 20 + padding; Canvas.TextOut(left, y, array[i].name, text_color, TA_LEFT); Canvas.TextOut(right, y, array[i].value, text_color, TA_RIGHT); } //--- canvas update Canvas.Update(); }最後に、プロパティ値を取得して出力できます。コードにOnChartEvent関数ハンドラがない場合は、追加します。そこで、CHARTEVENT_CHART_CHANGEイベントIDを確認します。イベントが発生したら、いくつかの変数を宣言してプロパティ値を取得し、それらを構造体配列に渡し、Redraw関数を呼び出します。それで完成です。インジケーターをコンパイルしてチャートに追加し、チャートを操作してキャンバスの更新を確認できます。
//+------------------------------------------------------------------+ //| Custom indicator chart event handler function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { if(id != CHARTEVENT_CHART_CHANGE) return; int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); int chart_scale = (int)ChartGetInteger(0, CHART_SCALE); int chart_first_vis_bar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); int chart_vis_bars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); double chart_prcmin = ChartGetDouble(0, CHART_PRICE_MIN); double chart_prcmax = ChartGetDouble(0, CHART_PRICE_MAX); //--- StrProperty array[] { {"Width", (string)chart_width}, {"Height", (string)chart_height}, {"Scale", (string)chart_scale}, {"First Vis. Bar", (string)chart_first_vis_bar}, {"Visible Bars", (string)chart_vis_bars}, {"Price Min", (string)chart_prcmin}, {"Price Max", (string)chart_prcmax}, }; Redraw(array); }
座標変換
この時点で、日時またはバーインデックスからピクセル単位のxへの変換、価格からピクセル単位のyへの変換、xからバーインデックスへの変換、yから価格への変換をおこなうためのいくつかの基本的な関数が必要です。今全部使用するわけではありませんが、一度にすべてを作成します。そのため、チャートプロパティ変数をグローバルスコープに移動しますが、OnChartEvent関数では値を更新するだけで、必要に応じてRedraw関数を呼び出します。理想的な解決策は変数と変換関数をクラスまたは構造体にカプセル化することですが、ここでは単純にしておきましょう。ただし、「オブジェクト指向プログラミング」とドキュメントの関連トピック(「オブジェクト指向プログラミング」)を読んで、OOPの学習を開始することをお勧めします。それを次の機会に生かしていきます。
関数は基本的に比例関係にあるため、説明に時間と言葉を費やすことはありません。それらは次のとおりです。
//+------------------------------------------------------------------+ //| Converts the chart scale property to bar width/spacing | //+------------------------------------------------------------------+ int BarWidth(int scale) {return (int)pow(2, scale);} //+------------------------------------------------------------------+ //| Converts the bar index(as series) to x in pixels | //+------------------------------------------------------------------+ int ShiftToX(int shift) {return (chart_first_vis_bar - shift) * BarWidth(chart_scale) - 1;} //+------------------------------------------------------------------+ //| Converts the price to y in pixels | //+------------------------------------------------------------------+ int PriceToY(double price) { // avoid zero divider if(chart_prcmax - chart_prcmin == 0.) return 0.; return (int)round(chart_height * (chart_prcmax - price) / (chart_prcmax - chart_prcmin) - 1); } //+------------------------------------------------------------------+ //| Converts x in pixels to bar index(as series) | //+------------------------------------------------------------------+ int XToShift(int x) { // avoid zero divider if(BarWidth(chart_scale) == 0) return 0; return chart_first_vis_bar - (x + BarWidth(chart_scale) / 2) / BarWidth(chart_scale); } //+------------------------------------------------------------------+ //| Converts y in pixels to price | //+------------------------------------------------------------------+ double YToPrice(int y) { // avoid zero divider if(chart_height == 0) return 0; return chart_prcmax - y * (chart_prcmax - chart_prcmin) / chart_height; }
透明なDRAW_FILLING
これで、CCanvasを使用してDRAW_FILLINGを実装するために必要なものがすべて揃いました。
新しいインジケーターの作成に時間はかかりません。代わりに、すべてのMetaTrader 5プラットフォームに存在する例を取り上げて、2つの行の間に塗りつぶしを追加してみましょう。ターミナルデータフォルダの「\\MQL5\\Indicators\\Examples\\」にあるエンベロープを使用します。Envelopes.mq5を、ChartPropertiesViewerインジケーターを作成したのと同じディレクトリにコピーします。任意のインジケーターを選択できますが、この記事で説明されている手順に従って同じインジケーターを使用することをお勧めします.
最初に必要なのは、ChartPropertiesViewerインジケーターでおこなったすべてをエンベロープにコピーすることです。
上記のように、2つの線の間のチャネルを埋めます。この目的のために、これらの行の値に対応する配列を渡す関数を作成します。エンベロープインジケーターでは、配列はExtUpBuffer変数およびExtMABuffer変数によって指定されます。
double ExtUpBuffer[]; double ExtDownBuffer[];
配列とともに、2つの色を使用し、透明度を設定し、インジケーターをチャートの左または右に移動できるようにするいくつかの変数を渡します。
パラメータ | 変数の記述 |
---|---|
serie1 | 最初の行に対応する値の配列 |
serie2 | 2行目に対応する値の配列 |
clr1 | serie1>=serie2の場合の色 |
clr2 | serie1<serie2の場合の色 |
alpha | チャネルの透明度の値 |
plot_shift | チャートの右または左のインジケーターをシフト |
既存の変数と上記のパラメータを使用した関数は次のとおりです。
//+------------------------------------------------------------------+ //| Fill the area between two lines | //+------------------------------------------------------------------+ void DrawFilling(double &serie1[], double &serie2[], color clr1, color clr2, uchar alpha = 255, int plot_shift = 0) { int start = chart_first_vis_bar; int total = chart_vis_bars + plot_shift; uint argb1 = ColorToARGB(clr1, alpha); uint argb2 = ColorToARGB(clr2, alpha); int limit = fmin(ArraySize(serie1), ArraySize(serie2)); int px, py1, py2; for(int i = 0; i < total; i++) { int bar_position = start - i; int bar_shift = start - i + plot_shift; int bar_index = limit - 1 - bar_shift; if(serie1[bar_index] == EMPTY_VALUE || serie1[bar_index] == EMPTY_VALUE || bar_shift >= limit) continue; int x = ShiftToX(bar_position); int y1 = PriceToY(serie1[bar_index]); int y2 = PriceToY(serie2[bar_index]); uint argb = serie1[bar_index] < serie2[bar_index] ? argb2 : argb1; if(i > 0 && serie1[bar_index - 1] != EMPTY_VALUE && serie2[bar_index - 1] != EMPTY_VALUE) { if(py1 != py2) Canvas.FillTriangle(px, py1, px, py2, x, y1, argb); if(y1 != y2) Canvas.FillTriangle(px, py2, x, y1, x, y2, argb); } px = x; py1 = y1; py2 = y2; } }
この時点まで、固定サイズのキャンバスを使用してきました。ただし、インジケーターの描画には、チャートの全領域を埋めるためのキャンバスが必要です。さらに、最大化、最小化、いずれかの側への拡大、またはサブウィンドウインジケーターの追加によってチャートウィンドウのサイズが変更されるたびに、キャンバスが引き続きチャートの全領域を占めるようにする必要があります。そのために、OnChartEvent関数に小さな変更を加えてキャンバスのサイズを変更します。
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { if(id != CHARTEVENT_CHART_CHANGE) return; chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); chart_scale = (int)ChartGetInteger(0, CHART_SCALE); chart_first_vis_bar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); chart_vis_bars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); chart_prcmin = ChartGetDouble(0, CHART_PRICE_MIN, 0); chart_prcmax = ChartGetDouble(0, CHART_PRICE_MAX, 0); if(chart_width != Canvas.Width() || chart_height != Canvas.Height()) Canvas.Resize(chart_width, chart_height);
次に、機能させるためにいくつかの小さな更新を追加します。
- Redraw関数を更新して、前のインジケーターに追加されたパラメータを削除し、DrawFilling関数を追加する
- OnCalculationにRedraw関数を追加して、インジケーターの値が変化したときに描画を更新する
- CreateBitmapLabelを呼び出すときにパラメータとして渡されるオブジェクト名を変更する
//+------------------------------------------------------------------+ //| Custom indicator redraw function | //+------------------------------------------------------------------+ void Redraw(void) { uint default_color = 0; color clrup = (color)PlotIndexGetInteger(0, PLOT_LINE_COLOR, 0); color clrdn = (color)PlotIndexGetInteger(1, PLOT_LINE_COLOR, 0); //--- canvas erase Canvas.Erase(default_color); //--- add first draw DrawFilling(ExtUpBuffer, ExtDownBuffer,clrup, clrdn, 128, InpMAShift); //--- canvas update Canvas.Update(); }
//--- the main loop of calculations for(int i=start; i<rates_total && !IsStopped(); i++) { ExtUpBuffer[i]=(1+InpDeviation/100.0)*ExtMABuffer[i]; ExtDownBuffer[i]=(1-InpDeviation/100.0)*ExtMABuffer[i]; } Redraw(); //--- OnCalculate done. Return new prev_calculated. return(rates_total);
Canvas.CreateBitmapLabel(0, 0, short_name, 0, 0, 200, 150, COLOR_FORMAT_ARGB_NORMALIZE);
これで全部です。期間の異なる2つのエンベロープと1つの長方形オブジェクトで、現在どのように見えるかを確認できます。
ご覧のとおり、インジケーターの問題は解決しましたが、チャートオブジェクトの問題はまだ存在しています。これが次の章の課題です。
サブウィンドウインジケーターで機能するようにメソッドを拡張する
下の図を参照してください。ここでは、DRAW_FILLINGを使用したサブウィンドウインジケーターを見ることができます。この図は、MQLドキュメントから取得したものです。同じことをおこないますが、CCanvasを使用して透過性を許可し、さらに重要なこととして、オーバーラップエリアの問題を回避します。
必要な変更は次のとおりです。
- インジケーターが配置されている同じサブウィンドウにビットマップラベルを作成する
- メインチャートウィンドウではなく、サブウィンドウのサイズに基づいてキャンバスのサイズを変更する
同じサブウィンドウでビットマップラベルを作成し、サブウィンドウのサイズを取得するには、インジケーターが配置されているサブウィンドウを見つける必要があります。これは単にチャートの最後のサブウィンドウであると考えることができますが、ターミナルでは、必ずしも最後のサブウィンドウではなく、同じサブウィンドウに2つ以上のインジケーターを配置できます。次に、インジケーターが配置されているサブウィンドウの番号を返す関数が必要です。次の関数を見てください。
//+------------------------------------------------------------------+ //| return the number of the subwindow where the indicator is located| //+------------------------------------------------------------------+ int ChartIndicatorFind(string shortname) { int subwin = ChartGetInteger(0, CHART_WINDOWS_TOTAL); while(subwin > 0) { subwin--; int total = ChartIndicatorsTotal(0, subwin); for(int i = 0; i < total; i++) { string name = ChartIndicatorName(0, subwin, i); if(name == shortname) return subwin; } } return -1; }
最後の指標では、エンベロープ指標を例として使用しました。ここでの例のソースとして、ドキュメントのDRAW_FILLINGをトピックにあるコードを使用します。以前に2つのインジケーターを作成したのと同じディレクトリに新しいインジケーターを作成できます。「SubwindowIndicator」と名付けましょう。次に、ドキュメントからコードをコピーします。
このインジケーターはDRAW_FILLINGを使用してプロットされます。CCanvasを使用してチャネルを埋めるために、プロットタイプを線に置き換えます。以下は、インジケーターのプロパティの変更です。
#property indicator_plots 2 //--- plot Intersection #property indicator_label1 "Fast" #property indicator_type1 DRAW_LINE #property indicator_color1 clrRed #property indicator_width1 1 #property indicator_label2 "Slow" #property indicator_type2 DRAW_LINE #property indicator_color2 clrBlue #property indicator_width2 1
そして、次はOnInit関数の変更です。
//--- indicator buffers mapping SetIndexBuffer(0,IntersectionBuffer1,INDICATOR_DATA); SetIndexBuffer(1,IntersectionBuffer2,INDICATOR_DATA); //--- PlotIndexSetInteger(0,PLOT_SHIFT,InpMAShift); PlotIndexSetInteger(1,PLOT_SHIFT,InpMAShift);
また、インジケーターが線の外観を変更する必要はありません。その場合、OnCalculate関数でこの行をコメント化できます。
//--- If a sufficient number of ticks has been accumulated if(ticks>=N) { //--- Change the line properties //ChangeLineAppearance(); //--- Reset the counter of ticks to zero ticks=0; }
これで、チャートプロパティ変数と、この記事で作成した関数を追加できます。このインジケーターでは、パラメータとしてDrawFilling関数に渡す必要がある配列の名前が異なります。そのため、Redraw関数で変更する必要があります。
double IntersectionBuffer1[]; double IntersectionBuffer2[];
Redraw関数は次のようになります。
//+------------------------------------------------------------------+ //| Custom indicator redraw function | //+------------------------------------------------------------------+ void Redraw(void) { uint default_color = 0; color clrup = (color)PlotIndexGetInteger(0, PLOT_LINE_COLOR, 0); color clrdn = (color)PlotIndexGetInteger(1, PLOT_LINE_COLOR, 0); //--- canvas erase Canvas.Erase(default_color); //--- add first draw DrawFilling(IntersectionBuffer1, IntersectionBuffer2, clrup, clrdn, 128, InpMAShift); //--- canvas update Canvas.Update(); }
コードをコンパイルすると、期待どおりの結果が得られます。
結論
この記事では、CCanvasの操作、いくつかのチャートプロパティ、それらの値を取得し、それらを使用して有用でさまざまな目的に適用できるいくつかの基本的な座標変換をおこなう方法を含む基本的なプロセスを見てきました。その後、透明性のあるインジケーターを開発することができました。そして、サブウィンドウインジケーターを操作する方法を拡張することが、私たちの最後の仕事でした。
この記事で開発されたインジケーターのファイルは、この記事の最後でダウンロードできます。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/12357
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索