MQL5でカスタムインジケーターを作成する(第3回):扇形と円形によるマルチゲージの強化
はじめに
前回の記事(第2回)では、MetaQuotes Language 5 (MQL5)でRSI(相対力指数)をゲージ形式で表示するインジケーターを作成しました。Canvas描画と針の仕組みを使い、RSIの値を円形ゲージとして可視化しています。針は値に応じて動的に変化し、買われすぎと売られすぎのゾーンは色分けで表現しています。さらに、従来の線グラフも併用することで、モメンタムの把握がしやすい構成にしました。第3回となる今回は、この仕組みをさらに拡張し、マルチゲージに対応させます。RSIに加えて、CCI(商品チャネル指数)やMFI(マネーフローインデックス)といった複数のオシレーターを、ユーザーが自由に組み合わせて表示できるようにします。また、ゲージの見た目も強化します。円形に加えて、扇形を扱えるようにし、それぞれを派生クラスとして実装します。円弧や多角形、相対配置を使った描画により、より柔軟で見栄えの良いマルチインジケーター表示を実現します。本記事では以下のトピックを扱います。
最終的には、複数オシレーターを視覚的に扱えるMQL5インジケーターが完成します。今後のカスタマイズにもそのまま活用できるはずです。それでは進めていきます。
扇形と円形のマルチゲージフレームワーク
マルチゲージフレームワークは、カスタマイズ可能なゲージの基盤クラスを土台として構築します。その上で、円形ゲージおよび扇形ゲージのスタイルを実装した派生クラスを導入し、RSI(相対力指数)、CCI(商品チャネル指数)、MFI(マネーフローインデックス)といったオシレーターを可視化できるようにします。表示方法は列挙型で制御し、単体表示と複合表示を切り替えられるようにすることで、複数インジケーターにわたる柔軟なモメンタム分析を実現します。円形ゲージでは、塗りつぶされた円をベースとした従来の円形のケースを維持します。一方、扇形ゲージは、円弧ベースのセクションや丸みを持たせた円弧、接続線、多角形などを組み合わせることで、部分的なダイヤル形状を表現し、より視覚的にリッチな表示を実現します。また、角度の範囲に応じて柔軟に形状を変えられるようにしつつ、相対配置によって複数のゲージをチャート上に横並びで配置できるようにしています。このアイデアは、ゲージ全体ではなく、一部だけを表示したい場合があることから生まれました。そこで、円形のゲージを半分や4分の1に分割し、用途に応じて選べるようにするべきだと考えました。実装では、これらを条件に応じて使い分けられるよう、3種類の表示をサポートします。
これを実現するために、基盤となるゲージクラスには、ケースの計算および描画をおこなう純粋仮想メソッドを定義し、派生クラス側でスタイルごとの処理をオーバーライドできるようにします。また、ゲージ選択用の列挙型を追加し、RSI(円形ゲージ)、CCI(扇形ゲージ)、MFI(扇形ゲージ)の各インスタンスを条件付きで初期化および配置できるようにします。さらに、それぞれのインジケーターからデータを取得するためのハンドルとバッファも統合します。これらは主に可視化のためのデータ取得に使用しますが、用途はこれに限りません。たとえば、損益の表示、インジケーターの流れ、進捗状況や口座情報のメトリクスなど、任意のデータを可視化する用途にも応用できます。アーキテクチャとしては、スケール(ゼロ位置の目盛り配置を強化)と針(テール長を調整可能)のレイヤーを分離しています。これにより、再描画効率を高めつつ、前のゲージを基準にした相対配置(アンカー)も安定しておこなえるようにしています。以下に想定される表示例を示します。

MQL5での実装
拡張実装を開始するにあたり、まずはインジケーターのプロットとバッファを調整し、追加するインジケーターに対応するためのプロパティを拡張します。具体的には、今回新たに対応するCCIおよびMFIのための設定を追加します。以下にその方法を説明します。
#property indicator_buffers 3 #property indicator_plots 3 #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_style1 STYLE_SOLID #property indicator_width1 2 #property indicator_label1 "RSI" #property indicator_type2 DRAW_LINE #property indicator_color2 clrGreen #property indicator_style2 STYLE_SOLID #property indicator_width2 2 #property indicator_label2 "CCI" #property indicator_type3 DRAW_LINE #property indicator_color3 clrBlue #property indicator_style3 STYLE_SOLID #property indicator_width3 2 #property indicator_label3 "MFI" #property indicator_minimum 0 #property indicator_maximum 100 #property indicator_level1 30 #property indicator_level2 70 #property indicator_level3 -100 #property indicator_level4 100 #property indicator_level5 0 #property indicator_levelcolor clrGray #property indicator_levelstyle STYLE_DOT
まず、#propertyディレクティブを使ってインジケーターのメタデータを再定義します。今回は3つのインジケーターを扱うため、データ格納用にindicator_buffersで3つのバッファを割り当て、indicator_plotsでも3つのプロットを設定します。1つ目のプロットは従来どおりRSI用で、タイプはDRAW_LINE、色はdodger blue、スタイルは実線、太さ2、ラベルは「RSI」とします。2つ目はCCI用で、緑色の実線、太さ2、ラベルは「CCI」、3つ目はMFI用で、青色の実線、太さ2、ラベルは「MFI」とします。
さらに、indicator_minimumとindicator_maximumを使って縦軸スケールを0〜100に設定し、indicator_level1〜indicator_level5、indicator_levelcolor、indicator_levelstyleを用いて、30、70、-100、100、0の5つのレベルラインを灰色の点線で追加し、各オシレーターの閾値を参照できるようにします。これらの設定により、RSI、CCI、MFIを別ウィンドウ上で同時にグラフ表示できるようになります。
また、インジケーターの入力ダイアログから、RSI、CCI、MFIのいずれか、あるいはその組み合わせを選択して表示できるようにすることで、柔軟性とリソース効率を高めます。さらに、特にCCIのように負の値を取り、ゼロが中央に来るインジケーターに対応したスケール描画を正しくおこなうために、ゼロ位置(null)をどこに配置するかを制御する列挙型も定義しておきます。
//+------------------------------------------------------------------+ //| Gauge Selection Enum | //+------------------------------------------------------------------+ enum ENUM_GAUGE_SELECTION { // Define gauge selection enum RSI_ONLY, // RSI Only CCI_ONLY, // CCI Only MFI_ONLY, // MFI Only RSI_CCI, // RSI CCI RSI_MFI, // RSI MFI CCI_MFI, // CCI MFI ALL // All }; // Inputs input ENUM_GAUGE_SELECTION inpGaugeSelection = ALL; // Gauge Selection //+------------------------------------------------------------------+ //| Null Mark Position Enum | //+------------------------------------------------------------------+ enum ENUM_NULLMARK_POS { // Define null mark position enum NULLMARK_NONE=0, // None NULLMARK_LEFT=1, // Left NULLMARK_MIDDLE=2, // Middle NULLMARK_RIGHT=3 // Right };
表示するゲージを選択するためのオプションを提供するために、ENUM_GAUGE_SELECTION列挙型を定義します。これには、RSI_ONLY、CCI_ONLY、MFI_ONLYといった個別選択に加え、RSI_CCI、RSI_MFI、CCI_MFIのような組み合わせ、さらにすべてを表示するALLを含めます。また、ENUM_GAUGE_SELECTION型の入力パラメータinpGaugeSelectionを宣言し、デフォルト値をALLに設定することで、ユーザーがインジケーター設定から直接ゲージ構成を選択できるようにします。続いて、スケール上のゼロ位置(null)を指定するためのENUM_NULLMARK_POS列挙型を定義します。値としては、NULLMARK_NONEを0、NULLMARK_LEFTを1、NULLMARK_MIDDLEを2、NULLMARK_RIGHTを3とし、特に負の値を扱うインジケーターにおいても柔軟にスケールレイアウトを制御できるようにします。こうした定義により、想定されるさまざまなシナリオに対応できるようにしています。
次におこなうのは、ケースの構造を拡張して、いわゆる扇形ゲージにも対応できるようにすることと、針の後方部分(テール)の倍率を含むゲージ入力パラメータの拡張です。まずはケースの構造から見ていきます。
以前のケースの構造:
//+------------------------------------------------------------------+ //| Case Structure | //+------------------------------------------------------------------+ struct Struct_Case { // Define case structure bool display; // Store display flag Struct_Circle circle; // Store circle structure };
新しいケースの構造:
//+------------------------------------------------------------------+ //| Case Structure | //+------------------------------------------------------------------+ struct Struct_Case { // Define case structure bool display; // Store display flag Struct_Circle circle; // Store circle structure int mode; // Store mode Struct_Arc mainArc; // Store main arc Struct_Arc secondaryArc; // Store secondary arc Struct_Arc centerArc; // Store center arc Struct_Arc leftRoundingArc; // Store left rounding arc Struct_Arc rightRoundingArc; // Store right rounding arc Struct_Line leftConnectLine; // Store left connect line Struct_Line rightConnectLine; // Store right connect line Struct_Dot fillDot; // Store fill dot };
ここでは、Struct_Case構造体を拡張し、より高度なゲージケースの描画、特に扇形ゲージに対応できるようにします。そのために、表示フラグと基本的な円要素を扱うStruct_Circleを内部に持たせます。さらに、角度範囲に応じて枠構成を切り替えるための整数modeを追加し、メイン円弧用および大きな角度に対応する補助円弧用のStruct_Arcに加え、中央円弧やエッジを滑らかにするための左右の円弧も用意します。また、各パーツをつなぐために左右の接続線としてStruct_Lineを追加し、多角形塗りつぶし時に隙間が出ないよう補完するための塗りつぶし用の点としてStruct_Dotも組み込みます。一方、パラメータ構造体については、針のテールの倍率を指定する入力を追加するのみとし、以下のように定義します(該当箇所は分かりやすいようにハイライトしています)。
//+------------------------------------------------------------------+ //| Gauge Input Parameters Structure | //+------------------------------------------------------------------+ struct Struct_GaugeInputParams { // Define gauge input parameters structure int xOffset; // Store x offset int yOffset; // Store y offset int anchorCorner; // Store anchor corner int relativeMode; // Store relative mode string relativeObjectName; // Store relative object name int scaleAngleRange; // Store scale angle range int rotationAngle; // Store rotation angle color scaleColor; // Store scale color int scaleStyle; // Store scale style bool displayScaleArc; // Store display scale arc flag double minScaleValue; // Store minimum scale value double maxScaleValue; // Store maximum scale value int scaleMultiplier; // Store scale multiplier int tickStyle; // Store tick style int tickSize; // Store tick size double majorTickInterval; // Store major tick interval int mediumTicksPerMajor; // Store medium ticks per major int minorTicksPerInterval; // Store minor ticks per interval int tickFontSize; // Store tick font size string tickFontName; // Store tick font name bool tickFontItalic; // Store tick font italic flag bool tickFontBold; // Store tick font bold flag color tickFontColor; // Store tick font color Struct_RangeParams ranges[4]; // Store ranges array color caseColor; // Store case color int borderStyle; // Store border style color borderColor; // Store border color int borderGapSize; // Store border gap size Struct_GaugeLegendParams description; // Store description Struct_GaugeLegendParams units; // Store units Struct_GaugeLegendParams multiplier; // Store multiplier Struct_GaugeLegendParams value; // Store value int needleCenterStyle; // Store needle center style color needleCenterColor; // Store needle center color color needleColor; // Store needle color int needleFillStyle; // Store needle fill style double needleTailMultiplier; // Store needle tail multiplier };
今回追加した乗数用の変数によって、針のテールの長さを調整できるようになります(たとえば扇形ゲージでは短くするなど)。これにより、見た目のバランスが取りやすくなり、ゲージ形状ごとのフィット感も向上します。続いて、クラス構成を大きく見直し、ポリモーフィズムと継承を導入することで、設計の整理と将来的な拡張性を高めます。そのために、以下のようにprivateヘルパーを持つ2つの派生クラスを追加します。
//+------------------------------------------------------------------+ //| Base Gauge Class | //+------------------------------------------------------------------+ class CGaugeBase // Define base gauge class { private: int relativeX; //--- Store relative X int relativeY; //--- Store relative Y int centerX; //--- Store center X int centerY; //--- Store center Y double currentValue; //--- Store current value bool initializationComplete; //--- Store initialization complete flag void Draw(); //--- Declare draw method void CalculateNeedle(); //--- Declare calculate needle method void RedrawNeedle(double value); //--- Declare redraw needle method void CalculateAndDrawLegends(); //--- Declare calculate and draw legends method void CalculateAndDrawLegendString(Struct_GaugeLegendString &legendString); //--- Declare calculate and draw legend string method void RedrawScaleMarks(Struct_Case &internalCase, Struct_Arc &scaleArc, int borderGap); //--- Declare redraw scale marks method void CalculateRanges(int borderGap); //--- Declare calculate ranges method bool IsValidRange(int index); //--- Declare check valid range method void NormalizeRangeValues(double &minValue, double &maxValue, double val0, double val1); //--- Declare normalize range values method void CalculateRangePie(Struct_Range &range, int innerRadius, int radialGap, int outerRadius, double rangeStart, double rangeEnd, color rangeClr, color caseClr); //--- Declare calculate range pie method void DrawRanges(); //--- Declare draw ranges method void DrawRange(Struct_Range &range); //--- Declare draw range method void CalculateInnerOuterRadii(int &innerRadius, int &outerRadius, int baseRadius, int tickLength, int tickStyle); //--- Declare calculate inner outer radii method bool DrawTick(double angle, int length, Struct_Arc &scaleArc); //--- Declare draw tick method double CalculateAngleDelta(double angle1, double angle2, int direction); //--- Declare calculate angle delta method bool GetLabelAreaSize(Struct_LabelAreaSize &areaSize, Struct_GaugeLegendString &legendString); //--- Declare get label area size method bool EraseLegendString(Struct_GaugeLegendString &legendString, color eraseClr); //--- Declare erase legend string method bool RedrawValueDisplay(double value); //--- Declare redraw value display method void SetLegendStringParams(Struct_GaugeLegendString &legendString, Struct_GaugeLegendParams ¶m, int minRadius, int radiusDelta); //--- Declare set legend string params method protected: Struct_GaugeInputParams inputParams; //--- Store input parameters Struct_ScaleLayer scaleLayer; //--- Store scale layer Struct_NeedleLayer needleLayer; //--- Store needle layer int m_radius; //--- Store radius virtual void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) = 0; //--- Declare calculate case elements method virtual void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) = 0; //--- Declare draw case elements method public: bool Create(string name, int x, int y, int size, string relativeObjectName, int relativeMode, int corner, bool background, uchar scaleTransparency, uchar needleTransparency); //--- Declare create method bool CalculateLocation(); //--- Declare calculate location method void Redraw(); //--- Declare redraw method void NewValue(double value); //--- Declare new value method void Delete(); //--- Declare delete method void SetScaleParameters(int angleRange, int rotation, double minValue, double maxValue, int multiplier, int style, color scaleClr, bool displayArc = false); //--- Declare set scale parameters method void SetTickParameters(int style, int size, double majorInterval, int mediumPerMajor, int minorPerInterval); //--- Declare set tick parameters method void SetTickLabelFont(int fontSize, string fontName, bool italic, bool bold, color fontClr = clrBlack); //--- Declare set tick label font method void SetCaseParameters(color caseClr, int borderStyle, color borderClr, int borderGapSize); //--- Declare set case parameters method void SetLegendParameters(int legendType, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr = clrDarkGray); //--- Declare set legend parameters method void SetLegendParam(Struct_GaugeLegendParams &legendParam, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr = clrDarkGray); //--- Declare set legend param method void SetRangeParameters(int index, bool enable, double start, double end, color rangeClr); //--- Declare set range parameters method void SetNeedleParameters(int centerStyle, color centerClr, color needleClr, int fillStyle, double tailMultiplier = 2.0); //--- Declare set needle parameters method }; //+------------------------------------------------------------------+ //| Round Gauge Class | //+------------------------------------------------------------------+ class CRoundGauge : public CGaugeBase // Define round gauge class { protected: void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) override //--- Override calculate case elements { if(borderSize > 0) { //--- Check border size externalCase.circle.centerX = scaleLayer.scaleArc.centerX; //--- Set external center X externalCase.circle.centerY = scaleLayer.scaleArc.centerY; //--- Set external center Y externalCase.circle.radius = m_radius; //--- Set external radius externalCase.circle.clr = inputParams.borderColor; //--- Set external color externalCase.display = true; //--- Set display flag } else //--- Handle no border externalCase.display = false; //--- Set display flag false internalCase.circle.centerX = scaleLayer.scaleArc.centerX; //--- Set internal center X internalCase.circle.centerY = scaleLayer.scaleArc.centerY; //--- Set internal center Y internalCase.circle.radius = m_radius - borderSize; //--- Set internal radius internalCase.circle.clr = inputParams.caseColor; //--- Set internal color internalCase.display = true; //--- Set display flag } void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) override //--- Override draw case elements { if(externalCase.display) //--- Check external display scaleLayer.obj_Canvas.FillCircle(externalCase.circle.centerX, externalCase.circle.centerY, externalCase.circle.radius, ColorToARGB(externalCase.circle.clr, scaleLayer.transparency)); //--- Fill external circle if(internalCase.display) //--- Check internal display scaleLayer.obj_Canvas.FillCircle(internalCase.circle.centerX, internalCase.circle.centerY, internalCase.circle.radius, ColorToARGB(internalCase.circle.clr, scaleLayer.transparency)); //--- Fill internal circle } }; //+------------------------------------------------------------------+ //| Sector Gauge Class | //+------------------------------------------------------------------+ class CSectorGauge : public CGaugeBase // Define sector gauge class { private: void CaseCalculateSector(Struct_Case &caseStruct, int gap) //--- Declare calculate sector method { double fi0,fi1,fi2; //--- Declare angles double sa; //--- Declare scale range Struct_Arc referenceArc = scaleLayer.scaleArc; //--- Set reference arc if(referenceArc.endAngle > referenceArc.startAngle) //--- Check end > start sa = NormalizeRadians(referenceArc.endAngle - referenceArc.startAngle); //--- Set sa else //--- Handle wrap sa = NormalizeRadians(referenceArc.endAngle + (2 * M_PI - referenceArc.startAngle)); //--- Set sa if(sa > M_PI) //--- Check > PI caseStruct.mode = 1; //--- Set mode 1 else //--- Handle <= PI caseStruct.mode = 0; //--- Set mode 0 if(sa > M_PI) { //--- Check > PI caseStruct.mainArc.centerX = referenceArc.centerX; //--- Set main center X caseStruct.mainArc.centerY = referenceArc.centerY; //--- Set main center Y caseStruct.mainArc.radius = referenceArc.radius + gap; //--- Set main radius caseStruct.mainArc.startAngle = NormalizeRadians(referenceArc.startAngle); //--- Set main start angle caseStruct.mainArc.endAngle = NormalizeRadians(referenceArc.startAngle + sa * 0.55); //--- Set main end angle caseStruct.mainArc.clr = clrNONE; //--- Set main color caseStruct.secondaryArc.display = true; //--- Set secondary display caseStruct.secondaryArc.centerX = referenceArc.centerX; //--- Set secondary center X caseStruct.secondaryArc.centerY = referenceArc.centerY; //--- Set secondary center Y caseStruct.secondaryArc.radius = referenceArc.radius + gap; //--- Set secondary radius caseStruct.secondaryArc.startAngle = NormalizeRadians(referenceArc.endAngle - sa * 0.55); //--- Set secondary start angle caseStruct.secondaryArc.endAngle = NormalizeRadians(referenceArc.endAngle); //--- Set secondary end angle caseStruct.secondaryArc.clr = clrNONE; //--- Set secondary color } else { //--- Handle <= PI caseStruct.mainArc.centerX = referenceArc.centerX; //--- Set main center X caseStruct.mainArc.centerY = referenceArc.centerY; //--- Set main center Y caseStruct.mainArc.radius = referenceArc.radius + gap; //--- Set main radius caseStruct.mainArc.startAngle = NormalizeRadians(referenceArc.startAngle); //--- Set main start angle caseStruct.mainArc.endAngle = NormalizeRadians(referenceArc.endAngle); //--- Set main end angle caseStruct.mainArc.clr = clrNONE; //--- Set main color } caseStruct.leftRoundingArc.radius = gap; //--- Set left rounding radius caseStruct.leftRoundingArc.centerX = (int)(referenceArc.centerX - referenceArc.radius * MathCos(M_PI - referenceArc.endAngle)); //--- Set left rounding center X caseStruct.leftRoundingArc.centerY = (int)(referenceArc.centerY - referenceArc.radius * MathSin(M_PI - referenceArc.endAngle)); //--- Set left rounding center Y if(caseStruct.mode == 1) //--- Check mode 1 fi1 = referenceArc.endAngle + (2 * M_PI - sa) / 2; //--- Set fi1 else //--- Handle mode 0 fi1 = referenceArc.endAngle + M_PI * 0.5; //--- Set fi1 caseStruct.leftRoundingArc.startAngle = referenceArc.endAngle; //--- Set left start angle caseStruct.leftRoundingArc.endAngle = NormalizeRadians(fi1); //--- Set left end angle caseStruct.leftRoundingArc.clr = clrNONE; //--- Set left color caseStruct.rightRoundingArc.radius = gap; //--- Set right rounding radius caseStruct.rightRoundingArc.centerX = (int)(referenceArc.centerX - referenceArc.radius * MathCos(M_PI - referenceArc.startAngle)); //--- Set right rounding center X caseStruct.rightRoundingArc.centerY = (int)(referenceArc.centerY - referenceArc.radius * MathSin(M_PI - referenceArc.startAngle)); //--- Set right rounding center Y if(caseStruct.mode == 1) //--- Check mode 1 fi0 = referenceArc.startAngle - (2 * M_PI - sa) / 2; //--- Set fi0 else //--- Handle mode 0 fi0 = referenceArc.startAngle - M_PI * 0.5; //--- Set fi0 caseStruct.rightRoundingArc.startAngle = NormalizeRadians(fi0); //--- Set right start angle caseStruct.rightRoundingArc.endAngle = referenceArc.startAngle; //--- Set right end angle caseStruct.rightRoundingArc.clr = clrNONE; //--- Set right color caseStruct.centerArc.centerX = referenceArc.centerX; //--- Set center arc center X caseStruct.centerArc.centerY = referenceArc.centerY; //--- Set center arc center Y caseStruct.centerArc.radius = needleLayer.needleCenter.radius + gap; //--- Set center arc radius fi2 = MathArcsin(((double)caseStruct.leftRoundingArc.radius / (double)caseStruct.centerArc.radius)); //--- Calculate fi2 fi1 = NormalizeRadians(referenceArc.endAngle + fi2); //--- Set fi1 caseStruct.centerArc.startAngle = fi1; //--- Set center start angle fi1 = NormalizeRadians(referenceArc.startAngle - fi2); //--- Set fi1 caseStruct.centerArc.endAngle = fi1; //--- Set center end angle caseStruct.centerArc.clr = clrNONE; //--- Set center color if(caseStruct.mode == 1) { //--- Check mode 1 double angleOffset = M_PI - (sa / 2); //--- Calculate offset caseStruct.leftConnectLine.startX = (int)(caseStruct.leftRoundingArc.centerX + caseStruct.leftRoundingArc.radius * MathSin((caseStruct.secondaryArc.endAngle + angleOffset) - M_PI * 1.5)); //--- Set left start X caseStruct.leftConnectLine.startY = (int)(caseStruct.leftRoundingArc.centerY + caseStruct.leftRoundingArc.radius * MathCos((caseStruct.secondaryArc.endAngle + angleOffset) - M_PI * 1.5)); //--- Set left start Y caseStruct.leftConnectLine.endX = (int)(caseStruct.rightRoundingArc.centerX + caseStruct.rightRoundingArc.radius * MathSin((caseStruct.mainArc.startAngle - angleOffset) - M_PI * 1.5)); //--- Set left end X caseStruct.leftConnectLine.endY = (int)(caseStruct.rightRoundingArc.centerY + caseStruct.rightRoundingArc.radius * MathCos((caseStruct.mainArc.startAngle - angleOffset) - M_PI * 1.5)); //--- Set left end Y caseStruct.leftConnectLine.clr = clrNONE; //--- Set left color } else { //--- Handle mode 0 caseStruct.leftConnectLine.startX = (int)(caseStruct.leftRoundingArc.centerX + caseStruct.leftRoundingArc.radius * MathSin(caseStruct.leftRoundingArc.startAngle - M_PI)); //--- Set left start X caseStruct.leftConnectLine.startY = (int)(caseStruct.leftRoundingArc.centerY + caseStruct.leftRoundingArc.radius * MathCos(caseStruct.leftRoundingArc.startAngle - M_PI)); //--- Set left start Y fi2 = MathArcsin(((double)caseStruct.leftRoundingArc.radius / (double)caseStruct.centerArc.radius)); //--- Calculate fi2 fi1 = NormalizeRadians(caseStruct.mainArc.endAngle + fi2); //--- Set fi1 caseStruct.leftConnectLine.endX = (int)(referenceArc.centerX - caseStruct.centerArc.radius * MathCos(M_PI - fi1)); //--- Set left end X caseStruct.leftConnectLine.endY = (int)(referenceArc.centerY - caseStruct.centerArc.radius * MathSin(M_PI - fi1)); //--- Set left end Y caseStruct.leftConnectLine.clr = clrNONE; //--- Set left color caseStruct.rightConnectLine.startX = (int)(caseStruct.rightRoundingArc.centerX + caseStruct.rightRoundingArc.radius * MathSin(caseStruct.rightRoundingArc.endAngle)); //--- Set right start X caseStruct.rightConnectLine.startY = (int)(caseStruct.rightRoundingArc.centerY + caseStruct.rightRoundingArc.radius * MathCos(caseStruct.rightRoundingArc.endAngle)); //--- Set right start Y fi1 = NormalizeRadians(caseStruct.mainArc.startAngle - fi2); //--- Set fi1 caseStruct.rightConnectLine.endX = (int)(referenceArc.centerX - caseStruct.centerArc.radius * MathCos(M_PI - fi1)); //--- Set right end X caseStruct.rightConnectLine.endY = (int)(referenceArc.centerY - caseStruct.centerArc.radius * MathSin(M_PI - fi1)); //--- Set right end Y caseStruct.rightConnectLine.clr = clrNONE; //--- Set right color } fi1 = M_PI - NormalizeRadians(referenceArc.endAngle - (sa / 2)); //--- Set fi1 caseStruct.fillDot.x = (int)(referenceArc.centerX - caseStruct.centerArc.radius * MathCos(fi1)); //--- Set fill dot X caseStruct.fillDot.y = (int)(referenceArc.centerY - caseStruct.centerArc.radius * MathSin(fi1)); //--- Set fill dot Y caseStruct.fillDot.clr = clrNONE; //--- Set fill dot color } void RedrawSectorCase(Struct_Case &caseStruct) //--- Declare redraw sector case method { int polygonX[5]; //--- Declare polygon X int polygonY[5]; //--- Declare polygon Y scaleLayer.obj_Canvas.Pie(caseStruct.mainArc.centerX, caseStruct.mainArc.centerY, caseStruct.mainArc.radius, caseStruct.mainArc.radius, caseStruct.mainArc.startAngle, caseStruct.mainArc.endAngle, ColorToARGB(caseStruct.mainArc.clr, scaleLayer.transparency), ColorToARGB(caseStruct.mainArc.clr, scaleLayer.transparency)); //--- Draw main pie if(caseStruct.secondaryArc.display) //--- Check secondary display scaleLayer.obj_Canvas.Pie(caseStruct.secondaryArc.centerX, caseStruct.secondaryArc.centerY, caseStruct.secondaryArc.radius, caseStruct.secondaryArc.radius, caseStruct.secondaryArc.startAngle, caseStruct.secondaryArc.endAngle, ColorToARGB(caseStruct.secondaryArc.clr, scaleLayer.transparency), ColorToARGB(caseStruct.secondaryArc.clr, scaleLayer.transparency)); //--- Draw secondary pie scaleLayer.obj_Canvas.FillCircle(caseStruct.leftRoundingArc.centerX, caseStruct.leftRoundingArc.centerY, caseStruct.leftRoundingArc.radius, ColorToARGB(caseStruct.leftRoundingArc.clr, scaleLayer.transparency)); //--- Fill left rounding scaleLayer.obj_Canvas.FillCircle(caseStruct.rightRoundingArc.centerX, caseStruct.rightRoundingArc.centerY, caseStruct.rightRoundingArc.radius, ColorToARGB(caseStruct.rightRoundingArc.clr, scaleLayer.transparency)); //--- Fill right rounding if(caseStruct.mode == 0) //--- Check mode 0 scaleLayer.obj_Canvas.FillCircle(caseStruct.centerArc.centerX, caseStruct.centerArc.centerY, caseStruct.centerArc.radius, ColorToARGB(caseStruct.centerArc.clr, scaleLayer.transparency)); //--- Fill center arc if(caseStruct.mode == 0) { //--- Check mode 0 caseStruct.secondaryArc.display = false; //--- Set secondary display false polygonX[1] = caseStruct.leftConnectLine.startX; //--- Set polygonX1 polygonX[0] = caseStruct.leftConnectLine.endX; //--- Set polygonX0 polygonX[2] = caseStruct.leftRoundingArc.centerX; //--- Set polygonX2 polygonX[4] = caseStruct.mainArc.centerX; //--- Set polygonX4 polygonX[3] = caseStruct.fillDot.x; //--- Set polygonX3 polygonY[1] = caseStruct.leftConnectLine.startY; //--- Set polygonY1 polygonY[0] = caseStruct.leftConnectLine.endY; //--- Set polygonY0 polygonY[2] = caseStruct.leftRoundingArc.centerY; //--- Set polygonY2 polygonY[4] = caseStruct.mainArc.centerY; //--- Set polygonY4 polygonY[3] = caseStruct.fillDot.y; //--- Set polygonY3 scaleLayer.obj_Canvas.FillPolygon(polygonX, polygonY, ColorToARGB(caseStruct.leftConnectLine.clr, scaleLayer.transparency)); //--- Fill left polygon polygonX[3] = caseStruct.rightConnectLine.startX; //--- Set polygonX3 polygonX[4] = caseStruct.rightConnectLine.endX; //--- Set polygonX4 polygonX[2] = caseStruct.rightRoundingArc.centerX; //--- Set polygonX2 polygonX[0] = caseStruct.mainArc.centerX; //--- Set polygonX0 polygonX[1] = caseStruct.fillDot.x; //--- Set polygonX1 polygonY[3] = caseStruct.rightConnectLine.startY; //--- Set polygonY3 polygonY[4] = caseStruct.rightConnectLine.endY; //--- Set polygonY4 polygonY[2] = caseStruct.rightRoundingArc.centerY; //--- Set polygonY2 polygonY[0] = caseStruct.mainArc.centerY; //--- Set polygonY0 polygonY[1] = caseStruct.fillDot.y; //--- Set polygonY1 scaleLayer.obj_Canvas.FillPolygon(polygonX, polygonY, ColorToARGB(caseStruct.rightConnectLine.clr, scaleLayer.transparency)); //--- Fill right polygon } else { //--- Handle mode 1 polygonX[0] = caseStruct.leftConnectLine.endX; //--- Set polygonX0 polygonX[1] = caseStruct.leftConnectLine.startX; //--- Set polygonX1 polygonX[2] = caseStruct.leftRoundingArc.centerX; //--- Set polygonX2 polygonX[3] = caseStruct.fillDot.x; //--- Set polygonX3 polygonX[4] = caseStruct.rightRoundingArc.centerX; //--- Set polygonX4 polygonY[0] = caseStruct.leftConnectLine.endY; //--- Set polygonY0 polygonY[1] = caseStruct.leftConnectLine.startY; //--- Set polygonY1 polygonY[2] = caseStruct.leftRoundingArc.centerY; //--- Set polygonY2 polygonY[3] = caseStruct.fillDot.y; //--- Set polygonY3 polygonY[4] = caseStruct.rightRoundingArc.centerY; //--- Set polygonY4 scaleLayer.obj_Canvas.FillPolygon(polygonX, polygonY, ColorToARGB(caseStruct.leftConnectLine.clr, scaleLayer.transparency)); //--- Fill polygon } } protected: void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) override //--- Override calculate case elements { int totalGap = scaleLayer.externalScaleGap + scaleLayer.externalLabelArea + scaleLayer.borderGap; //--- Calculate total gap if(borderSize > 0) { //--- Check border size CaseCalculateSector(externalCase, totalGap + borderSize); //--- Calculate external sector externalCase.mainArc.clr = inputParams.borderColor; //--- Set main color externalCase.secondaryArc.clr = inputParams.borderColor; //--- Set secondary color externalCase.centerArc.clr = inputParams.borderColor; //--- Set center color externalCase.leftRoundingArc.clr = inputParams.borderColor; //--- Set left rounding color externalCase.rightRoundingArc.clr = inputParams.borderColor; //--- Set right rounding color externalCase.leftConnectLine.clr = inputParams.borderColor; //--- Set left connect color externalCase.rightConnectLine.clr = inputParams.borderColor; //--- Set right connect color externalCase.fillDot.clr = inputParams.borderColor; //--- Set fill dot color externalCase.display = true; //--- Set display flag } else //--- Handle no border externalCase.display = false; //--- Set display flag false CaseCalculateSector(internalCase, totalGap); //--- Calculate internal sector internalCase.mainArc.clr = inputParams.caseColor; //--- Set main color internalCase.secondaryArc.clr = inputParams.caseColor; //--- Set secondary color internalCase.centerArc.clr = inputParams.caseColor; //--- Set center color internalCase.leftRoundingArc.clr = inputParams.caseColor; //--- Set left rounding color internalCase.rightRoundingArc.clr = inputParams.caseColor; //--- Set right rounding color internalCase.leftConnectLine.clr = inputParams.caseColor; //--- Set left connect color internalCase.rightConnectLine.clr = inputParams.caseColor; //--- Set right connect color internalCase.fillDot.clr = inputParams.caseColor; //--- Set fill dot color internalCase.display = true; //--- Set display flag } void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) override //--- Override draw case elements { if(externalCase.display) //--- Check external display RedrawSectorCase(externalCase); //--- Redraw external case if(internalCase.display) //--- Check internal display RedrawSectorCase(internalCase); //--- Redraw internal case } };
ここでは、CGaugeBaseクラスを定義し、位置情報、現在値、初期化フラグといったprivateメンバーを保持します。また、描画処理、針の計算、凡例表示、スケールマーク、レンジ、目盛り、ラベルなどのための各種メソッドをこれまで通り宣言しますが、CalculateCaseElementsとDrawCaseElementsについては純粋仮想関数として定義し、サブクラス側での実装を必須とします。一方で、セッターや更新処理などの共通ロジックについては、publicおよびprotectedセクションでデフォルト実装を提供します。次に、CGaugeBaseを継承したCRoundGaugeクラスを作成し、CalculateCaseElementsをオーバーライドして外側および内側の円を構成します。これには枠線サイズを基準とした外周、内周の設定、スケールの円弧から算出される中心座標、半径の調整、入力カラーや表示フラグの反映が含まれます。またDrawCaseElementsもオーバーライドし、表示対象となっている場合にはFillCircleを使用してこれらの円をスケールCanvas上に描画し、ARGB変換された色で塗りつぶします。
一方、CSectorGaugeクラスもCGaugeBaseを継承し、privateメソッドCaseCalculateSectorを追加します。このメソッドではセクター要素の計算をおこない、角度範囲がπを超えるかどうかでmodeを判定し、大きな角度の場合はメイン円弧と補助円弧を設定します。またMathArcsinやNormalizeRadiansを用いて左右の丸み部分を計算し、中心部分の円弧にはオフセットを適用します。さらにsinとcosを用いて左右の接続ラインをmodeに応じて調整し、中間角度位置を点で塗りつぶします。CSectorGaugeではCalculateCaseElementsをオーバーライドし、外部要素および枠線を考慮した全体のオフセットを計算します。その後、枠線が正の場合は外部ケースとして、内部ケースは適切なオフセットを用いてCaseCalculateSectorを呼び出します。さらに、すべての円弧、線、点のコンポーネントに対して枠線またはケースの色を割り当て、それぞれの表示を有効化します。
さらにDrawCaseElementsをオーバーライドし、外側および内側の表示フラグに応じてRedrawSectorCaseを呼び出します。この処理ではPieによるメインおよびサブセクターの描画、FillCircleによる丸い部分の塗りつぶし、そして接続部分を補完する多角形生成(座標配列を用いたFillPolygon)をおこないます。modeが0の場合は左右に分割された多角形、modeが1の場合は単一の多角形として処理を切り替えます。
全体として、この設計により純粋仮想関数を活用したポリモーフィズムが実現されます。RSIは円形、CCIやMFIは扇形ゲージを採用することで、それぞれのスケール特性に適した表現が可能になります(特にCCIのようなゼロ中心スケールに適しています)。Create内での相対配置により複数ゲージの横並び配置も自動化され、RedrawScaleMarksの改修によってCCIの負値スケールやMFIの反転スケール(100→0)にも対応し、正確な目盛り配置とラベル表示を実現します。calc_digits関数は小数点桁数を動的に調整し、表示の視認性も向上させます。以下に、使用した関数実装ロジックを示します。
//+------------------------------------------------------------------+ //| Calculate Digits | //+------------------------------------------------------------------+ int calc_digits(double value) { int i, j, max_nulls = 0, nulls = 0; //--- Declare variables if(value == 0) return(0); //--- Return 0 if zero ulong v = ulong(MathAbs(value) * 100000000); //--- Calculate v ulong vtmp; //--- Declare vtmp for(j = -5; j <= 5; j++) { //--- Loop j nulls = 0; //--- Reset nulls vtmp = v + (ulong)j; //--- Set vtmp for(i = 0; i < 8; i++) { //--- Loop i if(vtmp % 10 == 0) { //--- Check mod 10 == 0 vtmp = vtmp / 10; //--- Divide vtmp nulls++; //--- Increment nulls } else break; //--- Break else } if(max_nulls < nulls) max_nulls = nulls; //--- Update max nulls } return(8 - max_nulls); //--- Return digits }
calc_digits関数は、値を表示する際に必要な小数点以下の桁数を算出し、浮動小数点の精度問題にも対応するためのものです。値が0の場合は即座に0を返します。その後、絶対値に100,000,000を掛けて小数部分を整数側にシフトし、unsigned long型のvとして扱います。次にjを-5から5まで変化させながら微小なオフセットを加えたvtmpを生成し、それぞれについて末尾のゼロ数をカウントします。vtmpを10で割り続け、余りが0である間は最大8回まで繰り返し、その結果として得られるゼロの個数をmax_nullsとして記録します。最終的に8からmax_nullsを引いた値を有効な桁数として返します。最後におこなうべきことは、初期化時にゲージを条件付きで生成する部分の実装です。そのために、まずグローバル変数の構造を変更する必要があります。
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CRoundGauge rsiGauge; //--- Declare RSI gauge CSectorGauge cciGauge; //--- Declare CCI gauge CSectorGauge mfiGauge; //--- Declare MFI gauge int rsiHandle = INVALID_HANDLE; //--- Initialize RSI handle int cciHandle = INVALID_HANDLE; //--- Initialize CCI handle int mfiHandle = INVALID_HANDLE; //--- Initialize MFI handle double scaleMultipliers[9] = {10000, 1000, 100, 10, 1, 0.1, 0.01, 0.001, 0.0001}; //--- Define scale multipliers array string scaleMultiplierStrings[9] = {"x10k", "x1k", "x100", "x10", " ", "/10", "/100", "/1k", "/10k"}; //--- Define multiplier strings array double rsiBuffer[], cciBuffer[], mfiBuffer[]; //--- Declare buffers
ここでは、ゲージクラスのグローバルインスタンスを宣言します。RSIの可視化には円形スタイルを使用するCRoundGaugeとしてrsiGaugeを定義し、CCIとMFIについては扇形デザインを用いるCSectorGaugeとしてcciGaugeとmfiGaugeを定義します。また、それぞれのテクニカル指標を参照するためのハンドルとしてrsiHandle、cciHandle、mfiHandleを宣言し、初期値はINVALID_HANDLEに設定します。さらに、ゲージ上の表示スケーリングを調整するために、10000から0.0001までの9段階のスケール係数を持つscaleMultipliers配列を定義します。これに対応するラベルとして、scaleMultiplierStrings配列を用意し、凡例表示で現在の倍率を視覚的に確認できるようにします。最後に、各インジケーターの計算結果を保持するためのバッファとして、rsiBuffer、cciBuffer、mfiBufferの各double配列を宣言します。これらは後続の描画処理およびゲージ更新に使用されます。これでゲージの初期化処理に進む準備が整いました。
//+------------------------------------------------------------------+ //| Initialize Indicator | //+------------------------------------------------------------------+ int OnInit() { bool showRSI = (inpGaugeSelection == RSI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == RSI_MFI || inpGaugeSelection == ALL); //--- Set show RSI bool showCCI = (inpGaugeSelection == CCI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show CCI bool showMFI = (inpGaugeSelection == MFI_ONLY || inpGaugeSelection == RSI_MFI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show MFI string prevName = ""; //--- Initialize prev name int baseX = 30; //--- Set base X int baseY = 30; //--- Set base Y IndicatorSetInteger(INDICATOR_LEVELS, 5); //--- Set levels SetIndexBuffer(0, rsiBuffer, INDICATOR_DATA); //--- Set RSI buffer SetIndexBuffer(1, cciBuffer, INDICATOR_DATA); //--- Set CCI buffer SetIndexBuffer(2, mfiBuffer, INDICATOR_DATA); //--- Set MFI buffer PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set RSI empty PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set CCI empty PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set MFI empty PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, 14 - 1); //--- Set RSI draw begin PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, 14 - 1); //--- Set CCI draw begin PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, 14 - 1); //--- Set MFI draw begin if(showRSI) { //--- Check show RSI if(!rsiGauge.Create("rsi_gauge", baseX, baseY, 230, "", 0, 0, false, 0, 0)) return(INIT_FAILED); //--- Create RSI gauge rsiGauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 3); //--- Set RSI case rsiGauge.SetScaleParameters(250, 0, 0, 100, 4, 0, clrBlack, false); //--- Set RSI scale rsiGauge.SetTickParameters(0, 2, 10, 1, 4); //--- Set RSI ticks rsiGauge.SetTickLabelFont(1, "Arial", false, false, clrBlack); //--- Set RSI font rsiGauge.SetRangeParameters(0, true, 0, 30, clrLimeGreen); //--- Set RSI range 0 rsiGauge.SetRangeParameters(1, true, 70, 100, clrCoral); //--- Set RSI range 1 rsiGauge.SetRangeParameters(2, true, 30, 70, clrYellow); //--- Set RSI range 2 rsiGauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set RSI range 3 rsiGauge.SetLegendParameters(0, true, "RSI", 8, -180, 20, "Arial", false, false, clrBlueViolet); //--- Set RSI legend 0 rsiGauge.SetLegendParameters(3, true, "2", 4, 180, 13, "Arial", true, false, clrRed); //--- Set RSI legend 3 rsiGauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1, 2.0); //--- Set RSI needle rsiGauge.Redraw(); //--- Redraw RSI rsiGauge.NewValue(0); //--- Set RSI value 0 prevName = "rsi_gauge"; //--- Set prev name rsiHandle = iRSI(_Symbol, _Period, 14, PRICE_CLOSE); //--- Get RSI handle if(rsiHandle == INVALID_HANDLE) return(INIT_FAILED); //--- Check RSI handle } if(showCCI) { //--- Check show CCI string relName = prevName; //--- Set rel name int relMode = (prevName != "") ? 1 : 0; //--- Set rel mode int posX = (prevName != "") ? 0 : baseX; //--- Set pos X int posY = baseY + 90; //--- Set pos Y if(!cciGauge.Create("cci_gauge", posX, posY, 230, relName, relMode, 0, false, 0, 0)) return(INIT_FAILED); //--- Create CCI gauge cciGauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 1); //--- Set CCI case cciGauge.SetScaleParameters(200, 0, -200, 200, 4, 0, clrBlack, false); //--- Set CCI scale cciGauge.SetTickParameters(0, 2, 100, 1, 4); //--- Set CCI ticks cciGauge.SetTickLabelFont(1, "Arial", false, false, clrBlack); //--- Set CCI font cciGauge.SetRangeParameters(0, true, -200, -100, clrCoral); //--- Set CCI range 0 cciGauge.SetRangeParameters(1, true, -100, 100, clrDodgerBlue); //--- Set CCI range 1 cciGauge.SetRangeParameters(2, true, 100, 200, clrLimeGreen); //--- Set CCI range 2 cciGauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set CCI range 3 cciGauge.SetLegendParameters(0, true, "CCI", 4, 0, 16, "Arial", false, false, clrBlueViolet); //--- Set CCI legend 0 cciGauge.SetLegendParameters(3, true, "1", 1, 0, 12, "Arial", true, false, clrRed); //--- Set CCI legend 3 cciGauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1, 1.5); //--- Set CCI needle cciGauge.Redraw(); //--- Redraw CCI cciGauge.NewValue(0); //--- Set CCI value 0 prevName = "cci_gauge"; //--- Set prev name cciHandle = iCCI(_Symbol, _Period, 14, PRICE_TYPICAL); //--- Get CCI handle if(cciHandle == INVALID_HANDLE) return(INIT_FAILED); //--- Check CCI handle } if(showMFI) { //--- Check show MFI string relName = prevName; //--- Set rel name int relMode = (prevName != "") ? 1 : 0; //--- Set rel mode int posX = (prevName != "") ? 0 : baseX; //--- Set pos X int posY = baseY + 90; //--- Set pos Y if(!mfiGauge.Create("mfi_gauge", posX, posY, 250, relName, relMode, 0, false, 0, 0)) return(INIT_FAILED); //--- Create MFI gauge mfiGauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 3); //--- Set MFI case mfiGauge.SetScaleParameters(120, -35, 100, 0, 3, 0, clrBlack, false); //--- Set MFI scale mfiGauge.SetTickParameters(0, 2, 10, 1, 4); //--- Set MFI ticks mfiGauge.SetTickLabelFont(0, "Arial", false, false, clrBlack); //--- Set MFI font mfiGauge.SetRangeParameters(0, true, 80, 100, clrRed); //--- Set MFI range 0 mfiGauge.SetRangeParameters(1, true, 20, 80, clrMagenta); //--- Set MFI range 1 mfiGauge.SetRangeParameters(2, true, 0, 20, clrGreen); //--- Set MFI range 2 mfiGauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set MFI range 3 mfiGauge.SetLegendParameters(0, true, "MFI", 4, -15, 14, "Arial", false, false, clrBlueViolet); //--- Set MFI legend 0 mfiGauge.SetLegendParameters(2, true, "", 4, -50, 10, "Arial", false, false, clrDimGray); //--- Set MFI legend 2 mfiGauge.SetLegendParameters(3, true, "0", 3, -80, 16, "Arial", true, false, clrRed); //--- Set MFI legend 3 mfiGauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1, 1.2); //--- Set MFI needle mfiGauge.Redraw(); //--- Redraw MFI mfiGauge.NewValue(0); //--- Set MFI value 0 mfiHandle = iMFI(_Symbol, _Period, 14, VOLUME_TICK); //--- Get MFI handle if(mfiHandle == INVALID_HANDLE) return(INIT_FAILED); //--- Check MFI handle } return(INIT_SUCCEEDED); //--- Return succeeded }
OnInitイベントハンドラでは、まずinpGaugeSelectionの入力値に基づいて各ゲージの表示フラグを決定します。RSIを含む選択であればshowRSIをtrueとし、同様にCCIおよびMFIについてもshowCCI、showMFIをそれぞれ設定し、対応するインジケーターの表示を条件分岐できるようにします。次に相対配置用の初期値としてprevNameを空文字で初期化し、基準座標としてbaseXを30、baseYを30に設定します。また、IndicatorSetIntegerを用いて5段階のインジケーターレベルを設定し、SetIndexBufferによりrsiBuffer、cciBuffer、mfiBufferをそれぞれインデックス0、1、2にバインドしてインジケーターデータとして扱えるようにします。さらにPlotIndexSetDoubleで3つのプロットすべての空値をEMPTY_VALUEに設定し、PlotIndexSetIntegerで描画開始位置をバー13に指定することで14期間計算との整合性を取ります。このあたりは設計方針によって変更される可能性があります。
showRSIがtrueの場合、rsiGaugeをCreateでbase位置およびサイズ230、相対なしで生成し、SetCaseParametersで枠色をmintcream、枠線スタイル1、枠線の色をlight sky blue、ギャップを3に設定します。続いてSetScaleParametersで250度スケール、値範囲0〜100、倍率4、black、円弧なしを指定します。SetTickParametersではスタイル0、サイズ2、主目盛り10、中間目盛り1、補助目盛り4を設定し、SetTickLabelFontでフォントArial、サイズ1、装飾なし、blackを指定します。SetRangeParametersでは0〜30をlimegreen、70〜100をcoral、30〜70をyellowとし、無効領域をgrayに設定します。SetLegendParametersではRSIラベルを半径8、角度-180、サイズ20、Arial、blue violetで表示し、値表示は半径4、角度180、サイズ13、Arialイタリック、redで設定します。SetNeedleParametersでセンター1、black、シャフトdimgray、塗りあり、テール2.0を指定し、RedrawとNewValue(0)を実行した後、prevNameをrsi_gaugeに更新し、iRSI(14,close)でrsiHandleを取得、無効ならINIT_FAILEDを返します。
showCCIがtrueの場合はprevNameを参照して相対配置をおこない、prevNameが空でなければmode1として扱い、xを0またはbase、yをbase+90に設定してcciGaugeを相対配置で生成します。枠設定はRSIと同様ですがギャップは1とし、スケールは-200〜200の200度構成にします。目盛りとフォント設定は共通とし、レンジは-200〜-100をcoral、-100〜100をdodgerblue、100〜200をlimegreen、その他を無効色とします。凡例はCCIラベルを中心に配置し、値表示や装飾もRSIと同様の構造で設定します。針はテール1.5とし、描画後に初期値0で更新し、prevNameをcci_gaugeに更新、iCCI(14,typical price)でcciHandleを取得し無効チェックをおこないます。
showMFIがtrueの場合も同様に相対配置をおこない、yをbase+90へ配置してmfiGaugeを生成します。サイズは250、枠ギャップは3、スケールは100〜0の逆方向120度構成、倍率3とします。目盛りとフォントは共通設定で、レンジは80〜100をred、20〜80をmagenta、0〜20をgreen、それ以外を無効色とします。凡例はMFIラベルを角度-15付近に配置し、追加の倍率表示や値表示も補助ラベルとして配置します。針のテールの長さは1.2とし、描画後に0で初期化、iMFI(14,tick volume)でmfiHandleを取得しチェックします。
最後にすべて正常に初期化された場合はINIT_SUCCEEDEDを返し、初期化処理を完了します。初期化後、次のような結果が表示されます。

ここではバッファはまだ空のままで、インジケーター自体もプロットされていませんが、ゲージの表示自体は問題なく動作しています。次に必要なのは、OnCalculateイベントハンドラ内で、選択されたインジケーターに対してのみ条件付きで計算処理をおこない、その結果をバッファへ反映させることです。
//+------------------------------------------------------------------+ //| Calculate Indicator | //+------------------------------------------------------------------+ int OnCalculate(const int ratesTotal, const int prevCalculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tickVolume[], const long &volume[], const int &spread[]) { bool showRSI = (inpGaugeSelection == RSI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == RSI_MFI || inpGaugeSelection == ALL); //--- Set show RSI bool showCCI = (inpGaugeSelection == CCI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show CCI bool showMFI = (inpGaugeSelection == MFI_ONLY || inpGaugeSelection == RSI_MFI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show MFI static datetime lastBarTime = 0; //--- Declare last bar time bool isNewBar = (ratesTotal > 0 && time[ratesTotal - 1] != lastBarTime); //--- Check new bar if(isNewBar) lastBarTime = time[ratesTotal - 1]; //--- Update last bar time if(showRSI) { //--- Check show RSI if(rsiHandle != INVALID_HANDLE && CopyBuffer(rsiHandle, 0, 0, ratesTotal, rsiBuffer) < 0) return(0); //--- Copy RSI buffer } else { //--- Handle no show ArrayFill(rsiBuffer, 0, ratesTotal, EMPTY_VALUE); //--- Fill RSI empty } if(showCCI) { //--- Check show CCI if(cciHandle != INVALID_HANDLE && CopyBuffer(cciHandle, 0, 0, ratesTotal, cciBuffer) < 0) return(0); //--- Copy CCI buffer } else { //--- Handle no show ArrayFill(cciBuffer, 0, ratesTotal, EMPTY_VALUE); //--- Fill CCI empty } if(showMFI) { //--- Check show MFI if(mfiHandle != INVALID_HANDLE && CopyBuffer(mfiHandle, 0, 0, ratesTotal, mfiBuffer) < 0) return(0); //--- Copy MFI buffer } else { //--- Handle no show ArrayFill(mfiBuffer, 0, ratesTotal, EMPTY_VALUE); //--- Fill MFI empty } if(isNewBar) { //--- Check new bar if(showRSI && rsiHandle != INVALID_HANDLE) { //--- Check RSI double val[1]; //--- Declare val if(CopyBuffer(rsiHandle, 0, 0, 1, val) > 0) rsiGauge.NewValue(val[0]); //--- Set RSI value } if(showCCI && cciHandle != INVALID_HANDLE) { //--- Check CCI double val[1]; //--- Declare val if(CopyBuffer(cciHandle, 0, 0, 1, val) > 0) cciGauge.NewValue(val[0]); //--- Set CCI value } if(showMFI && mfiHandle != INVALID_HANDLE) { //--- Check MFI double val[1]; //--- Declare val if(CopyBuffer(mfiHandle, 0, 0, 1, val) > 0) mfiGauge.NewValue(val[0]); //--- Set MFI value } } return(ratesTotal); //--- Return rates total }
OnCalculateイベントハンドラでは、まずinpGaugeSelectionの入力に基づいてshowRSI、showCCI、showMFIの各フラグを再評価し、この計算サイクルで処理対象となるインジケーターとゲージを決定します。次にstatic datetime lastBarTimeを用いて新しいバーの発生を検出し、最新のtime[0]と比較して異なればisNewBarをtrueとし、lastBarTimeを更新します。showRSIがtrueでかつハンドルが有効な場合は、CopyBufferを用いてRelative Strength Indexの履歴全体をrsiBufferへコピーし、失敗した場合は表示を隠すためにArrayFillでrsiBufferをEMPTY_VALUEで埋めます。同様にshowCCIおよびshowMFIについても、それぞれcciBufferおよびmfiBufferへCopyBufferでデータを転送し、失敗時はEMPTY_VALUEでクリアします。
同様にshowCCIおよびshowMFIについても、それぞれcciBufferおよびmfiBufferへCopyBufferでデータを転送し、失敗時はEMPTY_VALUEでクリアします。さらに新規バーが発生した場合で、かつ各インジケーターが有効かつハンドルが正常な場合には、CopyBufferでシフト0、取得数1の最新値を一時配列valへ取得し、成功時には対応するゲージへNewValue(val[0])を呼び出してリアルタイム更新をおこないます。最終的にratesTotalを返して計算処理を継続します。また、不要なオブジェクトの蓄積を防ぐため、適切なタイミングでゲージの削除処理をおこない、チャート上のオブジェクトが過剰に残らないよう注意します。
//+------------------------------------------------------------------+ //| Deinitialize Indicator | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { rsiGauge.Delete(); //--- Delete RSI gauge cciGauge.Delete(); //--- Delete CCI gauge mfiGauge.Delete(); //--- Delete MFI gauge ChartRedraw(); //--- Redraw chart }
OnDeinitイベントハンドラでは、rsiGauge、cciGauge、mfiGaugeそれぞれに対してDeleteメソッドを呼び出し、スケールや針などのレイヤーオブジェクトをチャート上から削除してクリーンアップをおこないます。その後ChartRedrawを呼び出して、インジケーター終了後に視覚的な残留物が一切残らないようチャートを更新します。これにより、デアクティベーション時の後処理が正しく完了します。コンパイルすると、次の結果が得られます。

この可視化から分かるように、インジケーターの計算、ゲージの描画、各種パラメータ設定、そして不要時の削除まで一連の処理が正しく機能しており、当初の目的は達成されています。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

結論
今回は、MQL5のゲージ型インジケーターを拡張し、RSI、CCI、MFIといった複数のオシレーターを、ユーザーが選択可能な組み合わせで扱えるようにしました。あわせて、円形ゲージと扇形ゲージを派生クラスとして導入し、円弧、多角形、相対配置を用いた枠描画により、複数ゲージを整列させた状態でより柔軟に表示できる構成にしています。このインジケーターは、スケールやレンジ、凡例、針といった各要素を柔軟に設定でき、異なる値のドメインを持つオシレーターにも対応できる実用的なマルチ分析ツールになっています。また、設計自体は拡張性を意識しているため、任意のカスタムデータ指標へ置き換えることも可能です。
取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20719
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
ラリー・ウィリアムズの『市場の秘密』(第3回):MQL5で非ランダムな市場の動きを証明する
MQL5で他の言語の実用的なモジュールを実装する(第6回):MQL5におけるPython風ファイルI/O操作
MQL5でのAI搭載取引システムの構築(第8回):アニメーション、タイミング指標、応答管理ツールによるUIの改善
データサイエンスとML(第47回):DeepARモデルによるPythonでの市場予測
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索