MQL5でかぎ足をマスターする(第1回):インジケーターの作成
はじめに
かぎ足は、実際の市場の動きに焦点を当てた特別なタイプの価格チャートです。これは何年も前に日本で生まれました。 通常のローソク足チャートのように一定の時間間隔ごとに更新されるのではなく、かぎ足は価格が特定の値幅だけ動いたときにのみ方向を変えます。そのため、かぎ足は非常にクリーンで、不要なノイズを取り除くのに優れたチャートです。
かぎ足チャートは時間に依存しないため、トレーダーはトレンドを明確に把握できます。価格が上昇すると、線は上向きに伸びます。価格が下がると、線は下方向へ伸びます。需要と供給の力関係が変化すると、チャートのスタイルも変化します。このシンプルな構造により、トレーダーは余計な要素に邪魔されることなくトレンドの強さを把握することができます。
通常のローソク足チャートでは、市場は一定の時間間隔ごとに新しいバーを形成します。一方、かぎ足チャートは異なる挙動をします。価格が上昇したときにのみ上方向へ伸び、価格が指定された反転値幅だけ下落したときにのみ下方向へ転換します。さらに、供給と需要の力関係が変化すると、かぎ足ラインの太さや色が変わります。この明確な動きこそが、かぎ足チャートをトレンド分析において非常に強力なものにしています。
独自のかぎ足システムを構築し始める前に、標準的なローソク足チャートと比較したときにかぎ足がどのように見えるのかをイメージしておくと理解しやすくなります。以下の例は、そのシンプルな概念比較を示しています。
伝統的なローソク足チャート

かぎ足

この2部構成の連載では、MQL5を用いて完全なかぎ足ベースの取引システムを構築します。第1回では、OBJ_TRENDなどのMQL5グラフィカルオブジェクトを使用して、メインチャートウィンドウ上に直接描画されるライブで完全に機能するリペイントしないかぎ足を作成します。これにより、市場の動きに即座に反応する、クリーンで正確なかぎ足の可視化を実現します。
第2回では、この仕組みを拡張し、かぎ足シグナルを検出して自動的に取引をオープンするロジックを実装します。本連載の最後には、かぎ足がどのように構築されるのかを正確に理解し、MQL5内で動作する自分自身のかぎ足取引エンジンを持つことができるようになります。
かぎ足構築の基本
コーディングを始める前に、かぎ足がどのように構築されるのかを簡単に理解しておく必要があります。インターネット上には多くの詳細な説明があり、たとえばウィキにもよく知られた解説があります。そのため、ここでは理論全体を繰り返すことはせず、MQL5で正しくチャートを実装するために必要な本質的な要素に焦点を当てます。これらの概念は、方向の計算、反転の検出、そしてチャート上にかぎ足を描画する方法を考える際の指針になります。
価格変動と方向転換
かぎ足は、価格が十分に動いて意味のあるスイングを形成したときにのみ動きます。ここでは次の2つを追跡します。
- 上昇方向の動き
- 下降方向の動き
価格が同じ方向に動き続ける場合、かぎ足は単純にその方向へ延長されます。新しい方向に転換するのは、価格が既存のトレンドに対して、設定した反転幅を超えて逆方向へ動いた場合のみです。
肩(極大値)
肩(極大値)は、価格が新しい高値を付けた後に下落へ転じたときに現れます。その高値が肩レベルになります。その後、価格がその肩を上抜けると、強気の強さが確認され、ラインのスタイルが細線から太線へ変化するトリガーになります。
腰(極小値)
腰は極小値です。価格が新しい安値を付けた後に上昇へ転じたときに形成されます。その安値が腰のレベルになります。その後、価格がその腰を下抜けると、弱気の強さが確認され、ラインのスタイルが太線から細線へ変化するトリガーになります。
反転幅
反転幅は、かぎ足が方向を変えるタイミングを決定します。ここではトレーダーが次の2つの方法から選択できるようにします。
- 固定値
- 価格に対する割合
基本的な考え方はシンプルです。価格が現在の方向に対してこの値以上に逆方向へ動いた場合、かぎ足はまず水平セグメントを描画し、その後、反対方向へ新しい垂直セグメントを描画し始めます。
陰陽線
かぎ足では、供給と需要の変化を示すために2種類のラインスタイルを使用します。
- 陰線(細線):需要の弱まり、または売り圧力の優勢を示します。
- 陽線(太線): 需要の強まり、または買い圧力の優勢を示します。
陰から陽への切り替えは、かぎ足が以前の肩を上抜けたときに起こります。同様に、陽から陰への切り替えは、かぎ足が以前の腰を下抜けたときに起こります。
これらのスタイル変化は、かぎ足の代表的な売買シグナルを形成します。
- 薄い => 厚い = 買いシグナル
- 厚い => 薄い = 売りシグナル
プロジェクトの構造と入力
このセクションでは、かぎ足プロジェクトの基盤を準備します。本プログラムはエキスパートアドバイザー(EA)として構築します。MQL5ではスクリプト、インジケーター、EA、サービスといった複数のプログラムタイプがありますが、それぞれ用途が異なります。このプロジェクトでは、EA形式が最適です。なぜなら、メインチャートウィンドウ上でグラフィカルオブジェクトを完全に制御しながら操作できるためです。かぎ足のセグメントを価格チャート上に直接描画するために、トレンドラインのグラフィカルオブジェクトOBJ_TRENDを使用します。これにより、カスタムインジケーターバッファに依存せず、クリーンでインタラクティブな表示を実現できます。また、プロジェクト全体を1ファイルにまとめることができます。
ここからコードを書き始めます。MetaEditor 5を開き、新しいEAファイルを作成し、KagiTrader.mq5と名付けます。ファイルには、すべてのEAが依存する標準的なイベントハンドラが含まれます。具体的には、初期化(OnInit)、初期化解除(OnDeinit)、ティックハンドラ(OnTick)、および取引トランザクションハンドラ(OnTradeTransaction)です。これらがかぎ足ロジックの実行基盤となります。以下は、このEAの最小限のスケルトン構造です。ここから機能を拡張していきます。
//+------------------------------------------------------------------+ //| KagiTrader.mq5 | //| Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/ja/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/ja/users/chachaian" #property version "1.00" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ } //+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { } //+------------------------------------------------------------------+
初期化関数は、EAが起動したときに呼び出されます。ここにセットアップ用のロジックを配置します。初期化解除関数(OnDeinit)は、EAがチャートから削除されたときに呼び出され、後でグラフィカルオブジェクトをチャートからクリアする際に使用されます。ティックハンドラ(OnTick)は、市場価格の変化に応じてかぎ足を更新します。取引トランザクション関数(OnTradeTransaction)は第1回では必須ではありませんが、第2部で自動売買を追加する際に使用するため、構造上残しておきます。
プロジェクトの構造が整ったので、次に入力パラメータを定義します。これらの設定により、読者はかぎ足の挙動を自由にコントロールでき、コードを編集せずにチャートの調整が可能になります。
かぎ足インジケーター用の入力パラメータを定義する前に、まずカスタム列挙型を作成し、反転条件の解釈方法を指定します。かぎ足は、価格が一定の大きさで反転したときにのみ方向が変わりますが、「十分な反転」とみなす基準はトレーダーによって異なる場合があります。
この柔軟性を提供するために、次の列挙型を導入します。
//+------------------------------------------------------------------+ //| Custom Enumerations | //+------------------------------------------------------------------+ enum ENUM_KAGI_REVERSAL_TYPE { REVERSAL_BY_PERCENTAGE, REVERSAL_BY_PRICE_STEP };
この列挙により、インジケーターは反転を決定する2つのモードを持つことになります。
- REVERSAL_BY_PERCENTAGE
かぎ足は、価格が現在の方向に対して現在価格の指定パーセンテージだけ逆方向に動いたときに反転します。このオプションは市場のボラティリティに適応するため、反転の閾値が価格に応じて自動的にスケーリングされます。
- 価格ステップによる反転
ここでは、価格が固定の反転幅だけ動いたときに反転が発生します。この方式では、現在の価格水準に関わらず、動作が一定で予測しやすくなります。
以下に、入力パラメータのセクションとそれぞれの説明を示します。
//+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input group "Information" input ENUM_TIMEFRAMES kagiTimeframe = PERIOD_M10; input ENUM_KAGI_REVERSAL_TYPE reversalType = REVERSAL_BY_PERCENTAGE; input double reversalValue = 4.0; input color yangLineColor = C'38,166,154'; input color yinLineColor = C'239,83,80'; input bool overlayKagi = true;
各入力パラメータの説明は次のとおりです。
- kagiTimeframe
かぎ足を計算する際に使用する時間足を定義します。EAは、ユーザーが開いたチャートに関わらず、この時間足の価格データを読み取ります。
- reversalType
反転幅の解釈方法を制御します。トレーダーは固定値または価格に対する割合のどちらかを選択できます。これにより、市場の状況に応じた柔軟な設定が可能です。
- reversalValue
反転幅を指定します。選択された反転タイプに応じて、固定値またはパーセンテージとして設定します。
- yangLineColor
上昇または強気のかぎ足セグメントに使用される色を定義します。これらのセグメントは陽線を表します。
- yinLineColor
下降または弱気のかぎ足セグメントに使用される色を定義します。これらのセグメントは陰線を表します。
- overlayKagi
かぎ足を表示するかどうかを設定できます。この設定をオフにすると、EAはかぎ足セグメントを描画しなくなります。チャート挙動の比較やパフォーマンステスト時に便利です。
プロジェクトが進むにつれて、さらに入力パラメータを追加する可能性があります。たとえば、線の幅、描画オフセット、オブジェクトのクリーニングを制御するオプションなどです。現時点では、上記のパラメータでかぎ足の最初の動作するバージョンを構築するには十分です。
かぎ足の状態変数と内部データ構造
かぎ足の構築ロジックを実装する前に、チャートの内部状態を安全に保持および更新する方法を用意する必要があります。かぎ足は本質的に動的であり、価格変動、反転、トレンドの転換、陽線と陰線ラインの切り替えに常に反応します。これらすべての要素を管理するために、計算や描画の際に必要な重要な変数を格納するカスタム構造体を定義します。
入力パラメータの直後に、次の構造体を導入します。
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ struct MqlKagiData{ double closePrice[]; datetime openTime[]; double referencePrice; datetime referenceTime; double localMaximum; double localMinimum; bool isUptrend; bool isDowntrend; bool isYang; bool isYin; int lookBackBars; datetime lastBarOpenTime; }; //--- Initialize the state container MqlKagiData kagiData;
この構造体は、かぎ足インジケーターの内部「メモリ」として機能します。以下に各フィールドの説明と、その重要性を示します。
- closePrice[]
かぎ足は終値を基に構築されるため、この動的配列はチャートから取得した過去の終値を格納します。これらの値が各かぎ足セグメントの基礎となります。
- openTime[]
OBJ_TRENDなどのオブジェクトを使ってチャート上にラインを描画するには、価格と時間の両方の座標が必要です。この配列は、すべての過去バーの対応する開始時間を保持し、各かぎ足セグメントを正しくチャート上に配置できるようにします。
- referencePrice
現在のかぎ足を構築する際に使用する、最後の重要な価格を保持します。新しいチェックはバーの確定時のみおこなわれ、その終値に基づいてラインの延長か反転かが決定されます。
- referenceTime
referencePriceと同様、この変数はかぎ足構造の最後の更新に対応するタイムスタンプを保持します。かぎ足の変更をチャート上の正しい位置に描画するために使用されます。
- localMaximum
- localMinimum
最新の腰(直近の極小値)を格納します。価格が以前の安値を下抜けたときに、陰線への転換を検出するために必要です。
- isUptrend
現在のかぎ足が上昇中かどうかを示すブール値です。これにより、新しい価格がラインを延長するか反転を引き起こすかを判断できます。
- isDowntrend
isUptrendと同様、最新のかぎ足セグメントが下降中かどうかを示すフラグです。両方のフラグを保持することで、更新ロジックが明確かつ明示的になります。
- isYang
陽線は強さや需要を表します。価格が直近の肩を上抜けたときにtrueになります。チャート上で太線を描画すべきタイミングを判断するのに使用します。
- isYin
- lookBackBars
- lastBarOpenTime
処理された最新バーの開始時間を保持します。これにより、新しいバーが形成されたかどうかを検出し、インジケーターを必要なときだけ更新することが可能になります。
この構造体を定義し、kagiDataインスタンスを初期化することで、内部状態管理のための整理されたコンテナが用意されました。これにより、かぎ足セグメントの読み取り、更新、描画をクリーンかつ一貫した方法でおこなうことができます。
かぎ足の状態を初期化し、かぎ足処理のための履歴データをロードする
EAが起動したとき、内部状態を準備し、かぎ足の基礎となる過去データを読み込む必要があります。この初期化処理は一度だけ実行され、EAがチャートに描画を開始する前に必要なすべての設定をおこないます。
まず、単純なブールフラグや基本パラメータを設定します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- Initialize global variables kagiData.isUptrend = false; kagiData.isDowntrend = false; kagiData.isYang = false; kagiData.isYin = false; kagiData.lookBackBars = 100; kagiData.lastBarOpenTime = 0; return INIT_SUCCEEDED; }
チャートはまだ上昇傾向でも下降傾向でもないとマークします。 また、肩や腰が確認されていないため、陰陽フラグを両方ともfalseに設定します。 lookBackBarsの値は、描画のために表示しておく直近バーの数を制限します。 最後に、lastBarOpenTimeを0に設定することで、最初に処理されるバーが必ず新しいバーとして検出されるようにします。
次に、終値と始値の時刻を保持するための配列を準備します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Array Set As Series ArraySetAsSeries(kagiData.closePrice, true); ArraySetAsSeries(kagiData.openTime, true); return INIT_SUCCEEDED; }
ArraySetAsSeries関数をtrueで呼び出すと、配列は インデックス0に最新の要素が配置される形式になります。この並び順は、MetaTrader 5が履歴データをコピーして返す方法と一致しており、更新処理中のインデックス操作を簡単にします。また、シリーズ配列を使用することで、最新バーを処理する際のデータコピーおよびアクセスが高速化されます。
データをコピーする前に、利用可能な履歴データが十分に存在するかどうかを確認します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Get the total number of historical bars on both the lower and the higher timeframes int totalNumberOfHistoricalBarsOnKagiTimeframe = Bars(_Symbol, kagiTimeframe); if(totalNumberOfHistoricalBarsOnKagiTimeframe < 10){ return INIT_FAILED; } return INIT_SUCCEEDED; }
Bars関数は、選択した時間足および銘柄に対して読み込まれているローソク足の本数を返します。バー数が少なすぎる場合は、初期化処理を停止し、失敗ステータスを返します。これにより、後続処理でのエラーを防ぎ、データ不足の状態でEAが実行されるのを回避できます。
履歴データが十分に存在する場合は、次に 終値データをコピーします。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Get the actual close and opening times int closePricesCount = CopyClose(_Symbol, kagiTimeframe, 0, totalNumberOfHistoricalBarsOnKagiTimeframe, kagiData.closePrice); if(closePricesCount == -1){ Print("Error while copying historical close prices ", GetLastError()); return INIT_FAILED; } return INIT_SUCCEEDED; }
CopyCloseは、選択した時間足からバーの終値をclosePrice配列に格納します。返された数を確認し、関数が失敗した場合はエラーを出力して処理を中断します。この防御的なステップにより、不完全または欠落した価格データのままEAが処理を続行してしまうことを防ぎます。
その後、同じ方法でバーのオープン時刻もコピーします。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Get the actual close and opening times ... int openTimesCount = CopyTime(_Symbol, kagiTimeframe, 0, totalNumberOfHistoricalBarsOnKagiTimeframe, kagiData.openTime); if(openTimesCount == -1){ Print("Error while copying historical open times ", GetLastError()); return INIT_FAILED; } return INIT_SUCCEEDED; }
これらのタイムスタンプは、OBJ_TRENDのようなグラフィカルオブジェクトが価格座標と時間座標の両方を必要とするため必須です。ここでも戻り値を確認し、コピーに失敗した場合は処理を中断します。
この時点で、整列された2つの系列配列が揃いました。インデックス0の要素は、どちらも最新のバーを表します。必要に応じて、referencePriceとreferenceTimeをこれらの配列から初期化できます。これらの値は、履歴データから最新の確定バーまでの初期かぎ足セグメントを構築するために使用します。
チャートの外観の設定
かぎ足の構築を開始する前に、かぎ足の線がはっきり見えるようチャートを準備する必要があります。MetaTrader 5のデフォルトのチャート外観は、カスタムのグラフィカルオブジェクトを描画するには適していません。通常、色付きの背景、表示されたグリッド、そしてカスタムのかぎ足を隠したり見づらくしたりするローソク足が表示されます。そのため、チャートをシンプルで見やすいレイアウトに調整するための専用関数を作成します。
//+------------------------------------------------------------------+ //| This function configures the chart's appearance. | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){ Print("Error while setting chart background, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){ Print("Error while setting chart grid, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_MODE, CHART_LINE)){ Print("Error while setting chart mode, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){ Print("Error while setting chart foreground, ", GetLastError()); return false; } return true; }
ConfigureChartAppearance関数は、これらの視覚的プロパティを設定します。まず、チャートの背景色を白に変更します。これによりクリーンなベースが作られ、色付きのかぎ足がはるかに読みやすくなります。この処理に失敗した場合、関数はエラーメッセージを出力してfalseを返します。
次に、チャートグリッドを無効化します。グリッド線はかぎ足構造の視認性を妨げる可能性があるため、これを取り除くことで滑らかな表示を保つことができます。ここでもプラットフォームがこの設定を適用できない場合、関数は処理を停止してfalseを返します。
続いて、チャートモードをラインモードに設定します。かぎ足はOBJ_TRENDオブジェクトを使って描画するため、ローソク足やバーチャートよりもラインチャートの方が背景として分かりやすくなります。この状態では、私たちが描画するオブジェクトだけがはっきりと表示されます。チャートモードを適用できない場合、関数はfalseを返します。
最後に、前景色を黒に設定します。前景色は軸ラベルや基本的なチャート要素の色を制御します。白い背景に黒い前景色を組み合わせることで、かぎ足の視認性を損なうことなく良好な可読性を保つことができます。この処理が失敗した場合、関数は停止してエラーを報告します。
すべての設定が成功した場合、関数はtrueを返します。これによりEAはチャートが描画準備完了であることを認識できます。かぎ足を構築する前に、この関数を必ず呼び出す必要があります。最適な場所はOnInit関数の一番最初です。呼び出しは次のようになります。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- To configure the chart's appearance if(!ConfigureChartAppearance()){ Print("Error while configuring chart appearance", GetLastError()); return INIT_FAILED; } ... return INIT_SUCCEEDED; }
こうすることで、EAをアタッチするたびにチャートが適切に準備されます。これにより、かぎ足は最初から正しく表示され、描画オブジェクトが見えにくくなる問題を防ぐことができます。
履歴かぎ足の構築
履歴かぎ足を構築する前に、チャート上に線分を描画できるヘルパー関数群が必要です。かぎ足は価格の動きに応じて形状が何度も変化します。転換点で曲がり、上昇局面では太くなります。このため、各セグメントは個別のOBJ_TRENDオブジェクトを使って描画します。
以下の関数は描画作業をすべて処理してくれます。これによりコードが整理され、理解しやすくなります。4つの関数はすべて、指定された時間と価格座標を使ってチャート上にラインを作成します。また、線の色や幅を設定し、オブジェクトが誤って選択や移動されないようにします。線が正常に描画された場合はtrueを返し、エラーが発生した場合はfalseを返します。
//+------------------------------------------------------------------+ //| This function is used to draw a new yang line | //+------------------------------------------------------------------+ bool DrawYangLine(string line_name, datetime time1, double price1, datetime time2, double price2, color line_color = clrGreen, int line_width=5) { ResetLastError(); //--- Create a Yang Line if(!ObjectCreate(0, line_name, OBJ_TREND, 0, time1, price1, time2, price2)){ Print("Error while creating a Yin line: ", GetLastError()); return false; } //--- Set some vital object properties ObjectSetInteger(0, line_name, OBJPROP_COLOR, line_color); ObjectSetInteger(0, line_name, OBJPROP_WIDTH, line_width); ObjectSetInteger(0, line_name, OBJPROP_SELECTED, false); ObjectSetInteger(0, line_name, OBJPROP_SELECTABLE, false); ChartRedraw (0); return true; } //+------------------------------------------------------------------+ //| This function is used to draw a new yin line | //+------------------------------------------------------------------+ bool DrawYinLine(string line_name, datetime time1, double price1, datetime time2, double price2, color line_color = clrRed , int line_width=3) { ResetLastError(); //--- Create a Yin Line if(!ObjectCreate(0, line_name, OBJ_TREND, 0, time1, price1, time2, price2)){ Print("Error while creating a Yin line: ", GetLastError()); return false; } //--- Set some vital object properties ObjectSetInteger(0, line_name, OBJPROP_COLOR, line_color); ObjectSetInteger(0, line_name, OBJPROP_WIDTH, line_width); ObjectSetInteger(0, line_name, OBJPROP_SELECTED, false); ObjectSetInteger(0, line_name, OBJPROP_SELECTABLE, false); ChartRedraw (0); return true; } //+------------------------------------------------------------------+ //| This function is used to draw a bend top line | //+------------------------------------------------------------------+ bool DrawBendTop(string line_name, datetime time1, double price1, datetime time2, double price2, color line_color = clrGreen, int line_width=5) { ResetLastError(); //--- Create a Bend Top Line if(!ObjectCreate(0, line_name, OBJ_TREND, 0, time1, price1, time2, price2)){ Print("Error while creating a Bend Top line: ", GetLastError()); return false; } //--- Set some vital object properties ObjectSetInteger(0, line_name, OBJPROP_COLOR, line_color); ObjectSetInteger(0, line_name, OBJPROP_WIDTH, line_width); ObjectSetInteger(0, line_name, OBJPROP_SELECTED, false); ObjectSetInteger(0, line_name, OBJPROP_SELECTABLE, false); ChartRedraw (0); return true; } //+------------------------------------------------------------------+ //| This function is used to draw a bend bottom line | //+------------------------------------------------------------------+ bool DrawBendBottom(string line_name, datetime time1, double price1, datetime time2, double price2, color line_color = clrRed , int line_width=3) { ResetLastError(); //--- Create a Bend Bottom Line if(!ObjectCreate(0, line_name, OBJ_TREND, 0, time1, price1, time2, price2)){ Print("Error while creating a Bend Bottom line: ", GetLastError()); return false; } //-- Set some vital object properties ObjectSetInteger(0, line_name, OBJPROP_COLOR, line_color); ObjectSetInteger(0, line_name, OBJPROP_WIDTH, line_width); ObjectSetInteger(0, line_name, OBJPROP_SELECTED, false); ObjectSetInteger(0, line_name, OBJPROP_SELECTABLE, false); ChartRedraw (0); return true; }
- DrawYangLine
この関数は陽線を描画します。陽線は強さを表し、上昇局面で使用されます。関数は太めのトレンドラインを作成し、通常はユーザーが選択したYangカラーで表示されます。線の名前、2つの時間ポイント、2つの価格ポイント、およびオプションで色や幅の設定を受け取ります。線が作成されると、チャートは即座に更新されます。
- DrawYinLine
この関数は陰線を描画します。陰線は弱さを表し、下降局面で使用されます。操作方法はDrawYangLineと同じです。主な違いはデフォルトの色と線の幅で、陰線は通常細めに設定されます。これにより、伝統的なかぎ足の視覚スタイルが保たれます。
- DrawBendTop
この関数はかぎ足構造の上部での曲がり(トップベンド)を描画します。価格が新しい高値に達して下降に転じたときに曲がりが発生します。トップベンドラインは、この転換点の上部を形成します。関数は指定された座標を使ってトレンドラインを作成し、曲がりに適したスタイルを適用します。パラメータや内部ロジックは前述の関数と同じです。
- DrawBendBottom
この関数はかぎ足構造の下部での曲がり(ボトムベンド)を描画します。価格が新しい安値に達して上昇に転じたときにボトムベンドが発生します。他の関数と同様に動作し、転換点を示すクリーンな線分を作成します。
これら4つの関数は同じパターンに従っているため、構築ロジックで使用することでコードが簡潔かつ一貫性のあるものになります。かぎ足の各セグメントは、トレンドの方向や転換点の種類に応じて最適な関数を使って描画されます。
MQL5では、チャート上の各オブジェクトは固有の名前を持つ必要があります。同じ名前のオブジェクトは存在できません。かぎ足には多数の小さな線分が含まれ、チャートが大きくなるとセグメント数は非常に多くなります。すべてのオブジェクト名を配列に保持して手動で確認するのは現実的ではありません。この問題を解決するため、セグメントを描画するたびに新しい固有名を生成するGenerateUniqueName関数を使用します。
この関数は接頭辞と乱数を使って名前を作成し、過去の名前と重複しない確率を高めます。この関数は、チャートにまだ存在しない名前が生成されるまでループします。
//+------------------------------------------------------------------+ //| Function to generate a unique object name with a given prefix | //+------------------------------------------------------------------+ string GenerateUniqueName(string prefix) { int attempt = 0; string uniqueName; while(true) { uniqueName = prefix + IntegerToString(MathRand() + attempt); if(ObjectFind(0, uniqueName) < 0) break; attempt++; } return uniqueName; }
この関数の動作は次の通りです。
- カウンターを0に初期化して開始します。
- ループ内で候補となる名前を作成します。名前は、接頭辞+乱数+試行回数で構成されます。
- その名前のオブジェクトがチャート上に既に存在するかを確認します。
- 名前が空いていれば、ループを終了し、関数は新しい名前を返します。
- 名前が既に使われている場合は、試行カウンタを増加させ、空いている名前が見つかるまでループを続けます。
このシンプルな方法により、作成する各かぎ足セグメントが固有の識別子を持つことが保証されます。また、オブジェクト名を手動で管理する必要がなくなるため、描画ロジックもすっきり保たれます。
GenerateUniqueName関数は文字列接頭辞を必要とするため、新しいかぎ足セグメントを作成するたびに使用できる固定で信頼できる値が必要です。そのため、プロパティディレクティブの直下に以下のマクロを定義します。
//+------------------------------------------------------------------+ //| Macros | //+------------------------------------------------------------------+ #define TRENDLINE "standardKagi"
TRENDLINEマクロは、新しいかぎ足セグメントの名前を生成する際に使用する固定の接頭辞を定義します。これにより、すべてのオブジェクト名の出発点が一貫し、同じ文字列を何度も入力することで生じるミスを防ぐことができます。コード全体で単一のプレフィックスを使用することで、命名システムが整理され信頼性が高まり、GenerateUniqueName関数とスムーズに連携して、最終的な固有名を生成できます。
基盤となるコンポーネントが整ったので、最初の描画関数の構築に進めます。この関数は、初期化時にかぎ足チャート全体を構築する役割を持ちます。すべての履歴バーを処理し、かぎ足ロジックをステップごとに適用して、チャート上に初期ライン群を描画します。このステージを完了することで、リアルタイム更新が始まる前の基準となる完全なかぎ足構造が確立されます。この関数をConstructKagiOnInitializationと名付けます。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { }
まず、利用可能な最も古いバーから初期参照価格と時刻を設定します。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { //--- The very first historical bar serves as the initial reference point kagiData.referencePrice = kagiData.closePrice[ArraySize(kagiData.closePrice) - 1]; kagiData.referenceTime = kagiData.openTime [ArraySize(kagiData.openTime) - 1]; }
これにより、チャートの最も左端のバーがすべての将来の比較の基準点(アンカー)となります。この基準から、関数は履歴を順に進みながらかぎ足構造を構築していきます。
主要な処理は、古いバーから最新の確定バーまでを反復するループ内でおこなわれます。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { ... for(int i = ArraySize(kagiData.closePrice) - 2; i > 0; i--){ } }
ループでは、インデックス0の最新バーはスキップされます。これは、履歴の構築は最後に確定したバーまでしかおこなわないためです。ループの各反復では、1つのバーの終値とオープン時刻を処理し、その終値を次の継続または反転の候補価格として扱います。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { ... for(int i = ArraySize(kagiData.closePrice) - 2; i > 0; i--){ //--- During every iteration, we record the current bar’s close price and open time. double currentClosePrice = kagiData.closePrice[i]; datetime currentOpenTime = kagiData.openTime[i]; } }
各バーごとに反転量を計算します。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { ... for(int i = ArraySize(kagiData.closePrice) - 2; i > 0; i--){ ... double reversalAmount = 0.0; if(reversalType == 0){ reversalAmount = NormalizeDouble((reversalValue / 100.0) * kagiData.referencePrice, Digits()); } if(reversalType == 1){ reversalAmount = NormalizeDouble(reversalValue, Digits()); } } }
ユーザーがパーセンテージモードを選択した場合、反転は現在の基準価格の指定された割合で決まります。ユーザーが価格ステップモードを選択した場合、反転量は指定された固定値になります。どちらの場合も、値はシンボルの精度に合わせて正規化されます。
最初の重要な判断は、かぎ足の状態がまだニュートラルのときに初期トレンドの開始を判定することです。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { ... for(int i = ArraySize(kagiData.closePrice) - 2; i > 0; i--){ ... //--- Handle the initial execution when the EA is first attached to the chart. if(!kagiData.isUptrend && !kagiData.isDowntrend && !kagiData.isYang && !kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount)){ kagiData.isUptrend = true; kagiData.isYang = true; if(overlayKagi && i < kagiData.lookBackBars){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } if(!kagiData.isUptrend && !kagiData.isDowntrend && !kagiData.isYang && !kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount)){ kagiData.isDowntrend = true; kagiData.isYin = true; if(overlayKagi && i < kagiData.lookBackBars){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.localMinimum = currentClosePrice; kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; } } }
方向やスタイルのフラグが設定されておらず、終値が基準価格を上方向に反転量以上超えた場合、関数は上昇トレンドを開始し、そのラインをYangとしてマークします。終値が基準価格を下方向に反転量以上下回った場合は、下降トレンドを開始し、そのラインを陽線としてマークします。
次におこなうのは、シンプルなトレンド継続の判定です。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { ... for(int i = ArraySize(kagiData.closePrice) - 2; i > 0; i--){ ... //--- Handle a normal continuation if(kagiData.isUptrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } } }
現在のかぎ足状態が上向きの陽の場合、関数は終値が基準価格+反転量を超え、かつ最後に記録された局所高値を更新した場合にのみ陽線を延長します。下向きの陰の場合は、終値が基準価格−反転量を下回り、かつ最後に記録された局所安値を下回った場合にのみ陰線を延長します。
その後、関数は以前の極値内で発生する通常の反転を処理します。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { ... for(int i = ArraySize(kagiData.closePrice) - 2; i > 0; i--){ ... //--- Handle a normal reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice >= kagiData.localMinimum)){ if(overlayKagi && i < kagiData.lookBackBars){ DrawBendTop(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.isDowntrend = true; kagiData.isUptrend = false; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice <= kagiData.localMaximum)){ if(overlayKagi && i < kagiData.lookBackBars){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.isDowntrend = false; kagiData.isUptrend = true; } } }
上昇トレンドが反転条件を満たしても局所安値以上に留まる場合、トップベンドが描画され、新しい縦方向のセグメントが下向きに開始されます。逆に、下降トレンドが反転して上昇しても局所高値以下に留まる場合は、対称的に処理されます。
より複雑な反転は、反転が以前の局所極値を超えた場合に発生します。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { ... for(int i = ArraySize(kagiData.closePrice) - 2; i > 0; i--){ ... //--- Handle a complex reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice < kagiData.localMinimum)){ if(overlayKagi && i < kagiData.lookBackBars){ DrawBendTop (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, kagiData.localMinimum, yangLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.localMinimum, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMinimum = currentClosePrice; kagiData.isDowntrend = true; kagiData.isUptrend = false; kagiData.isYang = false; kagiData.isYin = true; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice > kagiData.localMaximum)){ if(overlayKagi && i < kagiData.lookBackBars){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, kagiData.localMaximum, yinLineColor); DrawYangLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.localMaximum, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMaximum = currentClosePrice; kagiData.isDowntrend = false; kagiData.isUptrend = true; kagiData.isYang = true; kagiData.isYin = false; } } }
この場合、関数はまずベンドを描画し、次に前の極値までの短いセグメントを描画し、最後に極値を超えて続く新しいセグメントを描画します。これにより、前の肩や腰を突破した反転を視覚的に表現する二段階のセグメント変化が作られます。
反転の処理後、コードにはトレンドの継続や逆反転のための細かい分岐が多数含まれています。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi on Initialization | //+------------------------------------------------------------------+ void ConstructKagiOnInitialization() { ... for(int i = ArraySize(kagiData.closePrice) - 2; i > 0; i--){ ... //--- Handle a normal continuation after reversal if(kagiData.isDowntrend && kagiData.isYang && (currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice >= kagiData.localMinimum))){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.referencePrice = currentClosePrice; } if(kagiData.isUptrend && kagiData.isYin && (currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice <= kagiData.localMaximum))){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.referencePrice = currentClosePrice; } //--- Handle a complex continuation after reversal if(kagiData.isDowntrend && kagiData.isYang && (currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice < kagiData.localMinimum))){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMinimum, yangLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMinimum, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; kagiData.isYang = false; kagiData.isYin = true; } if(kagiData.isUptrend && kagiData.isYin && (currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice > kagiData.localMaximum))){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYinLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMaximum, yinLineColor); DrawYangLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMaximum, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; kagiData.isYang = true; kagiData.isYin = false; } //--- Handle a normal counter-reversal if(kagiData.isDowntrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice <= kagiData.localMaximum){ if(overlayKagi && i < kagiData.lookBackBars ){ DrawBendTop(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.isUptrend = true; kagiData.isDowntrend = false; } if(kagiData.isUptrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice >= kagiData.localMinimum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.isUptrend = false; kagiData.isDowntrend = true; } //--- Handle a complex counter-reversal if(kagiData.isDowntrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawBendTop(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMaximum = currentClosePrice; kagiData.isUptrend = true; kagiData.isDowntrend = false; } if(kagiData.isUptrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMinimum = currentClosePrice; kagiData.isDowntrend = true; kagiData.isUptrend = false; } //--- Handle a normal continuation after counter-reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice <= kagiData.localMaximum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.referencePrice = currentClosePrice; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice >= kagiData.localMinimum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.referencePrice = currentClosePrice; } //--- Handle a complex continuation after counter-reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.referencePrice = currentClosePrice; kagiData.localMaximum = currentClosePrice; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.referencePrice = currentClosePrice; kagiData.localMinimum = currentClosePrice; } //--- Handle a weird scenario if(kagiData.isUptrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMaximum, yinLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMaximum, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.isYin = false; kagiData.isYang = true; kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } if(kagiData.isDowntrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ if(overlayKagi && i < kagiData.lookBackBars){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMinimum, yangLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMinimum, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.isYang = false; kagiData.isYin = true; kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } } }
これらの処理ブロックは、以前の極値内での継続、極値を超える継続、再度方向が反転するカウンター反転、新しい局所極値も設定する複雑なカウンター反転などのケースをカバーします。
各分岐は、内部状態変数を同じように更新して内部メモリの整合性を保ちます。描画呼び出しはすべてoverlayKagiフラグとlookBackBars制限で保護されており、関数は正確性のためにかぎ足履歴全体を計算しますが、表示に必要な最新セグメントのみを描画します。オブジェクト名は固有名ジェネレーターから取得され、描画される各セグメントは、陽線、陰線、トップベンド、ボトムベンドの適切なヘルパー関数を使用します。
ループの間、条件が満たされるたびにreferencePriceは最後のセグメントを定義する最新価格に移動し、referenceTimeは反転が発生した時刻を記録します。localMaximumとlocalMinimumは最新の肩や腰を記録し、ブールフラグは現在の方向やスタイルを反映します。
これらの更新により、ランタイムアップデーターは正確な状態から処理を継続できます。ループ終了時点で、内部のkagiDataには最後に確定したバーまでのすべての履歴を表す完全なかぎ足状態が保持されます。チャート上には、overlayが有効な場合、lookBackBars分のグラフィカルオブジェクトのみが表示されます。
この準備済みの状態から、リアルタイム更新関数は正しいコンテキストで新しい確定バーごとの処理を開始できます。
//+------------------------------------------------------------------+ //| This function is used to construct Kagi in real time | //+------------------------------------------------------------------+ void ConstructKagiInRealTime(double bidPr, double askPr){ if(IsNewBar(_Symbol, kagiTimeframe, kagiData.lastBarOpenTime)){ double currentClosePrice = iClose(_Symbol, kagiTimeframe, 1); datetime currentOpenTime = iTime( _Symbol, kagiTimeframe, 1); double reversalAmount = 0.0; if(reversalType == 0){ reversalAmount = NormalizeDouble((reversalValue / 100.0) * kagiData.referencePrice, Digits()); } if(reversalType == 1){ reversalAmount = NormalizeDouble(reversalValue, Digits()); } //--- Handle a normal continuation if(kagiData.isUptrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ if(overlayKagi){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } //--- Handle a normal reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice >= kagiData.localMinimum)){ if(overlayKagi){ DrawBendTop(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.isDowntrend = true; kagiData.isUptrend = false; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice <= kagiData.localMaximum)){ if(overlayKagi){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.isDowntrend = false; kagiData.isUptrend = true; } //--- Handle a complex reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice < kagiData.localMinimum)){ if(overlayKagi){ DrawBendTop (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, kagiData.localMinimum, yangLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.localMinimum, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMinimum = currentClosePrice; kagiData.isDowntrend = true; kagiData.isUptrend = false; kagiData.isYang = false; kagiData.isYin = true; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice > kagiData.localMaximum)){ if(overlayKagi){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, kagiData.localMaximum, yinLineColor); DrawYangLine (GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.localMaximum, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMaximum = currentClosePrice; kagiData.isDowntrend = false; kagiData.isUptrend = true; kagiData.isYang = true; kagiData.isYin = false; } //--- Handle a normal continuation after reversal if(kagiData.isDowntrend && kagiData.isYang && (currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice >= kagiData.localMinimum))){ if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.referencePrice = currentClosePrice; } if(kagiData.isUptrend && kagiData.isYin && (currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice <= kagiData.localMaximum))){ if(overlayKagi){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.referencePrice = currentClosePrice; } //--- Handle a complex continuation after reversal if(kagiData.isDowntrend && kagiData.isYang && (currentClosePrice <= (kagiData.referencePrice - reversalAmount) && (currentClosePrice < kagiData.localMinimum))){ if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMinimum, yangLineColor); DrawYinLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMinimum, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; kagiData.isYang = false; kagiData.isYin = true; } if(kagiData.isUptrend && kagiData.isYin && (currentClosePrice >= (kagiData.referencePrice + reversalAmount) && (currentClosePrice > kagiData.localMaximum))){ if(overlayKagi){ DrawYinLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMaximum, yinLineColor); DrawYangLine (GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMaximum, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; kagiData.isYang = true; kagiData.isYin = false; } //--- Handle a normal counter-reversal if(kagiData.isDowntrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice <= kagiData.localMaximum){ if(overlayKagi){ DrawBendTop(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.isUptrend = true; kagiData.isDowntrend = false; } if(kagiData.isUptrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice >= kagiData.localMinimum){ if(overlayKagi){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.isUptrend = false; kagiData.isDowntrend = true; } //--- Handle a complex counter-reversal if(kagiData.isDowntrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ if(overlayKagi){ DrawBendTop(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yangLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yangLineColor); } kagiData.localMinimum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMaximum = currentClosePrice; kagiData.isUptrend = true; kagiData.isDowntrend = false; } if(kagiData.isUptrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ if(overlayKagi){ DrawBendBottom(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, currentOpenTime, kagiData.referencePrice, yinLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), currentOpenTime, kagiData.referencePrice, currentOpenTime, currentClosePrice, yinLineColor); } kagiData.localMaximum = kagiData.referencePrice; kagiData.referencePrice = currentClosePrice; kagiData.referenceTime = currentOpenTime; kagiData.localMinimum = currentClosePrice; kagiData.isDowntrend = true; kagiData.isUptrend = false; } //Handle a normal continuation after counter-reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice <= kagiData.localMaximum){ if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.referencePrice = currentClosePrice; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice >= kagiData.localMinimum){ if(overlayKagi){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.referencePrice = currentClosePrice; } //--- Handle a complex continuation after counter-reversal if(kagiData.isUptrend && kagiData.isYang && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.referencePrice = currentClosePrice; kagiData.localMaximum = currentClosePrice; } if(kagiData.isDowntrend && kagiData.isYin && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ if(overlayKagi){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.referencePrice = currentClosePrice; kagiData.localMinimum = currentClosePrice; } //--- Handle a weird scenario if(kagiData.isUptrend && kagiData.isYin && currentClosePrice >= (kagiData.referencePrice + reversalAmount) && currentClosePrice > kagiData.localMaximum){ if(overlayKagi){ DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMaximum, yinLineColor); DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMaximum, kagiData.referenceTime, currentClosePrice, yangLineColor); } kagiData.isYin = false; kagiData.isYang = true; kagiData.localMaximum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } if(kagiData.isDowntrend && kagiData.isYang && currentClosePrice <= (kagiData.referencePrice - reversalAmount) && currentClosePrice < kagiData.localMinimum){ if(overlayKagi){ DrawYangLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.referencePrice, kagiData.referenceTime, kagiData.localMinimum, yangLineColor); DrawYinLine(GenerateUniqueName(TRENDLINE), kagiData.referenceTime, kagiData.localMinimum, kagiData.referenceTime, currentClosePrice, yinLineColor); } kagiData.isYang = false; kagiData.isYin = true; kagiData.localMinimum = currentClosePrice; kagiData.referencePrice = currentClosePrice; } } }
この関数は、新しいバーが確定したときにKagi構造を更新します。IsNewBar関数と保存されたlastBarOpenTimeを使って新しい確定バーを検出した後にのみ実行されます。関数は、選択されたかぎ足からiCloseとiTimeを使って、直前に確定したバーの終値とオープン時刻を取得します。
次に、反転量を初期化ルーチンと同じ方法で計算します。反転モードがパーセンテージの場合は現在のreferencePriceの指定割合を計算し、価格ステップモードの場合は固定値を使用します。値は銘柄の精度に合わせて正規化されます。
コアロジックは履歴構築関数と同じですが、新しいバー1本に対してのみ適用されます。トレンドの継続、通常反転、複雑な反転、カウンター反転、関連する継続ケースをチェックします。条件に一致すると、ヘルパー描画関数とGenerateUniqueNameを使って1本または複数のオブジェクトを描画します。描画はoverlayKagiinputパラメータがtrueの場合にのみおこなわれます。
描画後、関数は内部のかぎ足の状態を更新します。新しいセグメントや反転が発生した場合はreferencePriceとreferenceTimeを設定し、肩や腰を記録するためにlocalMaximumとlocalMinimumを調整します。必要に応じて、方向およびスタイルのフラグであるisUptrend、isDowntrend、isYang、isYinを切り替えます。
関数は現在のbidとaskをパラメータとして受け取りますが、かぎ足判断には確定バーの価格のみを使用します。関数の実行が終了すると、内部状態は次の新しいバーに備えて整い、overlayが有効な場合にはチャートに最新のかぎ足変化が反映されます。
EAイベントへの関数の組み込み
両方のコンストラクタ関数が準備できたので、適切なEAイベントハンドラーから呼び出す必要があります。履歴用コンストラクタは、初期化時に1回だけ呼び出します。これにより、履歴データから完全なかぎ足状態が構築され、初期セグメントがチャートに描画されます。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Construct Kagi On Initialization ConstructKagiOnInitialization(); return INIT_SUCCEEDED; }
毎ティックごとに、現在の市場価格を読み取り、リアルタイム用コンストラクタを呼び出します。リアルタイムルーチンはbidとaskをパラメータとして受け取るため、これらを渡します。かぎ足ロジック自体は判断に最新の確定バーの終値を使用します。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- Scope variables double askPrice = SymbolInfoDouble (_Symbol, SYMBOL_ASK); double bidPrice = SymbolInfoDouble (_Symbol, SYMBOL_BID); //--- Construct Kagi In Real Time ConstructKagiInRealTime(bidPrice, askPrice); }
これらの呼び出しを組み込むことで、EAは履歴から一度だけかぎ足を構築し、その後は新しいバーが確定するたびにかぎ足を更新し続けます。
記事の最後には、このEAの完全なソースコードが添付されています。ウォークスルーの途中で見逃した部分がある場合は、完全ファイルを開いて順番に各セクションを確認してください。完全ファイルには、プロパティブロック、列挙型、入力パラメータ、構造体、初期化関数、描画ヘルパー、固有名ジェネレーター、両方のコンストラクタ、および先ほど説明したイベントへの組み込みが含まれています。
EA の評価:かぎ足ロジックは正しく動作するか
Kagi Chart EAが完全に組み立てられたので、次のステップはライブチャートに配置して、期待通りに動作するか確認することです。デモとして、EAを日経平均株価指数(JPN225)のチャートにアタッチし、プロジェクトに付属する設定ファイルkagitrader.setをロードしました。設定を適用すると、EAをチャート上で起動し、すぐに結果が確認できました。かぎ足の構造は設計どおりに形成され始め、新しいバーが生成されるたびにスムーズに更新されました。下は、EAがリアルタイムで動作しているスクリーンショットで、価格推移の上に正しく描画されたかぎ足が表示されています。

結論
このプロジェクトのこの部分では、機能的なかぎ足チャートEAの基礎を完全に構築することに成功しました。チャート環境の準備、固有オブジェクト名の生成、内部かぎ足状態の管理、履歴データおよびリアルタイムでのチャート構築方法を学びました。これらのコンポーネントが連携することで、トレンドのスイング、反転、ラインの太さの変化を正確に追跡できる完全なかぎ足可視化ツールが手に入りました。これにより、市場構造を解釈し、情報に基づいた取引判断をおこなうための信頼できるチャートフレームワークが得られます。
EAをライブシンボルにアタッチし、提供されたkagitrader.setファイルでテストすることで、かぎ足ロジックが実際の環境でも正しく機能することを確認しました。EAはクリーンなセグメントを描画し、新しいバーごとに更新され、価格変動に対してルールどおりに反応します。これにより、MetaTrader 5内で完全に稼働するかぎ足エンジンを手に入れたことになります。
第2回では、この基盤をさらに発展させます。取引ロジックを追加し、設定コントロールを拡張し、意思決定機能を統合して、このチャートEAを真の取引ツールに変換します。次のステージの目標は、かぎ足の動作を活用してシグナルを生成し、エントリーとエグジットをガイドし、自動化戦略の開発をサポートすることです。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20239
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5入門(第29回):MQL5のAPIとWebRequest関数の習得(III)
MQL5での取引戦略の自動化(第42回):セッションベースのオープニングレンジブレイクアウト(ORB)システム
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5入門(第28回):MQL5のAPIとWebRequest関数の習得(II)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索