
MQL5における修正グリッドヘッジEA(第2部):シンプルなグリッドEAを作る
はじめに
連載「MQL5における修正グリッドヘッジEA」第2部へようこそ。 まず、第1部で説明したことをまとめましょう。第1部では、クラシックヘッジ戦略を検討し、EAを使用して自動化し、ストラテジーテスターでテストをおこない、いくつかの初期結果を分析しました。これは、クラシックヘッジ戦略とグリッド戦略のブレンドである、修正グリッドヘッジEAを作成するための私たちの旅の第一歩となりました。
前回、第2部ではクラシックヘッジ戦略の最適化に焦点を当てると述べました。しかし、予想外の遅れが生じたため、当面はクラシックグリッド戦略に焦点を移すことにします。
この記事では、クラシックグリッド戦略について掘り下げ、MQL5のEAで自動化し、ストラテジーテスターでテストをおこなって結果を分析し、戦略に関する有用な洞察を抽出します。
では、記事を深く掘り下げ、クラシックグリッド戦略のニュアンスを探ってみましょう。
この記事で取り上げる内容を簡単に紹介します。
クラシックグリッド戦略について
何よりもまず、先に進む前に戦略そのものを掘り下げてみましょう。
買いポジションからアプローチを開始しますが、売りポジションから始めることも同様に可能です。とりあえず、買いのシナリオに注目しましょう。分かりやすくするために、小さなロットサイズを使用して、特定の初期価格から開始します。たとえば、可能な最小ロットサイズである0.01としましょう。この時点から、価格が上昇または下落する可能性があるという2つのシナリオが考えられます。価格が上昇すれば利益を得ることになりますが、その程度は上昇幅によります。そして、価格があらかじめ決めた利食いレベルに達したら、この利益を収めます。問題は、価格が下落し始めたときです。そこで、利益を確保するためのセーフガードを提供する、クラシックグリッド戦略が登場します。
価格が一定量(説明しやすいように15 pipsとする)下がったら、別の買い注文を実行します。この注文は、最初の価格から15 pips引いた価格、ロットサイズを大きくして出します。簡単にするために、ロットサイズ乗数「2」を効果的に適用して、これを2倍にします。この新しい買い注文を出すと、再び2つのシナリオに直面します。価格が上昇すれば、ロットサイズが大きくなるため、この新規注文から利益を得ることができます。さらに、この上昇は利益をもたらすだけでなく、ロットサイズ0.01の最初の買い注文の損失も軽減します。純利益が0になるのは、価格が両注文の加重平均価格レベルに達したときです。この加重平均は以下のように計算できます。
この計算において、Xiは買い注文が出される価格を表し、w iはロットサイズ乗数によって決まる対応する重みを表します。ここまでで、加重平均価格レベルWは次のように計算できます。
W = 1 × ( 初値 ) + 2 × ( 初値 − 15 pips ) 3 ここで、X 1は初値、X 2は初値から15 pipsを引いた値、w 1は1、w 2は2です。
純利益が0になる時点は、価格レベルがこの算出された加重平均価格レベルに一致するときです。この概念は以下のように実演することができます。
分かりやすくして、初値がxで、ロットサイズ0.01の買い注文が出されたと考えましょう。その後、価格が15 pips下落したら、x - 15で新規買い注文を出し、ロットサイズを2倍の0.02にします。この時点の加重平均価格レベルは、( 1 × x + 2 × ( x − 15 ) ) / 3として計算されます。これは、x - 10となります。これは最初の価格レベルより10 pips低いです。
価格がx - 15に達したとき、0.01の最初の買い注文は1.5ドルの損失を発生させます(1ドルが10 pipsに相当するEURUSDペアを想定)。その後、価格が加重平均レベルx - 10まで上昇した場合、最初の注文で0.5ドルの損失を取り戻します。さらに、0.02の新規買い注文で、価格がx - 15のレベルから5 pips上昇したため、利益を得ました。ロットサイズが2倍になったため、ここでの利益も2倍になり、1ドルになりました。したがって、純利益の合計は0ドル(-1.5ドル+0.5ドル+1ドル)となります。
価格が加重平均レベルx - 10からさらに上昇した場合、利食いレベルに達するまで利益を獲得し続けます。
価格が上昇するどころか、加重平均価格レベルから下落し続ける場合は、同じ戦略を適用します。価格がx - 15をさらに15 pips下回った場合、x - 30で新しい買い注文が出され、ロットサイズは前回の2倍の0.04(0.02の2倍)になります。新しい加重平均価格レベルは、( 1 × x + 2 × ( x − 15 ) + 4 × ( x − 30 ) ) / 7として計算されます。これは、x - 21.428となります。最初の価格レベルより21.428 pips低くなります。
x - 30の価格レベルでは、0.01の最初の買い注文は3.0ドルのを発生させます(1ドルが10 pipsに相当するEURUSDペアを想定)。2回目の0.02の買い注文では、1.5ドルの2倍の3.0ドルの損失が発生し、合計6.0ドルの損失となります。しかし、その後価格が加重平均レベルx - 21.428まで上昇すれば、最初の買い注文0.01の損失は0.8572ドルで一部取り戻されます。さらに、2回目の注文の損失0.02ドルは、0.8572ドルの2倍の1.7144ドルで相殺されます。さらに、0.04の新規買い注文により、価格がx - 30のレベルから8.572 pips上昇したため、利益を得ます。ロットサイズが4倍(0.04)になるため、利益も4倍になり、0.8572ドルの4倍の3.4288ドルになります。したがって、純利益はほぼ0ドル(-6ドル+0.8572ドル+1.7144ドル+3.4288ドル=0.0004ドル)となります。加重平均物価レベルの計算で四捨五入しているため、この数字は正確には0ではありません。
加重平均レベルx - 21.428からさらに価格が上昇した場合、利食いレベルに達するまでプラスの利益を蓄積し続けます。逆に、価格が再び下落した場合は、このプロセスを繰り返し、繰り返すたびにロットサイズを2倍にし、それに応じて加重平均価格レベルを調整します。このアプローチにより、価格が加重平均価格レベルに達するたびに0ドルほどの純利益を一貫して達成し、徐々にプラスの利益へと導くことができます。
これと同じプロセスが売り注文にも適用され、最初の売り注文からサイクルが始まります。分解してみましょう。
ある初値での売りポジションで始めます。分かりやすくするために、ロットサイズは0.01(可能な最小サイズ)と小さくします。ここから、価格が上昇するか下落するかの2つの結果が考えられます。価格が下落した場合、その下落幅に応じて利益を得、価格が利益確定レベルに達したときに利益を収めます。しかし、価格が上昇し始めると状況は厳しくなり、その時点で、利益を確保するためにクラシックグリッド戦略が採用されます。
価格が一定量上昇したとします。説明しやすいように15 pipsとします。これに対して、初値+15 pipsで別の売り注文を出し、分かりやすくするためにロットサイズを2倍にします(ロットサイズ乗数は2)。この新しい売り注文を出した後、2つのシナリオがあります。価格が下落した場合、ロットサイズが大きくなるため、この新しい売り注文から利益を得ます。さらに、この減少は利益をもたらすだけでなく、ロットサイズ0.01の最初の売り注文の損失も減らします。価格が両方の注文の加重平均価格レベルに達したとき、純利益は0になります。これは、買い注文について話した際にすでに検討した概念です。
加重平均の計算式は、注文のそれぞれの価格とロットサイズを考慮し、変わりません。この戦略的アプローチにより、市場が変動しても、ポジションは損失から守られ、市場の動きに合わせて損益分岐点に達するまたは利益を得ることができます。
分かりやすくするために、ロットサイズ0.01の売り注文が発注された初期価格をxと仮定しましょう。その後、価格が15 pips上昇したら、x + 15で新規売り注文を出し、ロットサイズを倍の0.02にします。この段階での加重平均価格レベルは、( 1 × x + 2 × ( x + 15 ) ) / 3として計算されます。単純化するとx + 10、つまり最初の価格レベルより10 pips高くなります。
価格がx + 15に達すると、0.01の最初の売り注文は1.5ドルの損失を発生させます(1ドルが10 pipsに相当するEURUSDペアを想定)。その後、価格が加重平均レベルx + 10まで下落した場合、最初の売り注文で0.5ドルの損失を相殺します。さらに、0.02の新規売り注文により、価格がx + 15のレベルから5 pips下落したため、利益を得ます。ロットサイズが2倍になるため、利益も2倍となり、1ドルになります。したがって、純利益の合計は0ドル(-1.5ドル+0.5ドル+1ドルとして計算)となります。
価格が加重平均レベルx + 10から下がり続ければ、利食いレベルに達するまでプラスの利益が発生します。この戦略は、さまざまな値動きの中で損失と利益を効果的にバランスさせ、さまざまな市場環境下で純利益または損益分岐点のシナリオを確保します。
価格が、減少とは反対に、加重平均価格レベルから上昇し続ける場合は、前と同じ戦略を適用します。価格がx + 15レベルをさらに15 pips上回ったとしましょう。これに対し、x + 30で新たな売り注文を出し、ロットサイズを前回の2倍の0.04(0.02の2倍)とします。修正後の加重平均価格レベルは、( 1 × x + 2 × ( x + 15 ) + 4 × ( x + 30 ) ) / 7として計算されます。これは、x + 21.428、つまり最初の価格レベルより21.428 pips高くなります。
x + 30の価格レベルでは、0.01の最初の売り注文は3.0ドルの損失を発生させます(1ドルが10 pipsに相当するEURUSDペアを想定)。2回目の0.02の売り注文は、1.5ドルの2倍で3.0ドルの損失となり、合計6.0ドルの損失となります。しかし、その後価格が加重平均レベルx + 21.428まで下落した場合、最初の売り注文0.01の損失を0.8572ドルで部分的に回復します。さらに、0.02の2回目の注文の損失を、0.8572ドルの2倍である1.7144ドルで回収します。さらに、0.04の新規売り注文により、価格がx + 30のレベルから8.572 pips下落したため、利益を得ます。ロットサイズが4倍(0.04)になるため、利益も4倍になり、0.8572ドルの4倍の3.4288ドルになります。したがって、純利益の合計は約0ドル(-6ドル+0.8572ドル+1.7144ドル+3.4288ドル=0.0004ドルとして計算)となります。加重平均価格レベルの計算で四捨五入しているため、この数字は正確には0ではありません。
価格が加重平均レベルx + 21.428から下がり続ければ、利食いレベルに達するまでプラスの利益を積み重ねます。逆に、価格がさらに上昇した場合は、このプロセスを繰り返し、反復するたびにロットサイズを2倍にし、それに応じて加重平均価格レベルを調整します。このアプローチにより、価格が加重平均価格レベルに達するたびに0ドルほどの純利益を一貫して達成し、徐々にプラスの利益へと導くことができます。
クラシックグリッド戦略の自動化についての説明
では、EAを使用して、このクラシックグリッド戦略の自動化を掘り下げてみましょう。
まず、グローバル空間にいくつかの入力変数を宣言します。
input bool initialPositionBuy = true; input double distance = 15; input double takeProfit = 5; input double initialLotSize = 0.01; input double lotSizeMultiplier = 2;
- initialPositionBuy:初期ポジションのタイプ(売/買)を表すブーリアン変数(true:買い、false:売り)
- distance:各連続注文間の一定の距離(pipsで測定)
- takeProfit:すべての未決済ポジションの平均価格からpips単位で測定される距離
- initialLotSize:最初のポジションのロットサイズ
- lotSizeMultiplier:各連続ポジションのロットサイズに適用される乗数
これらは、戦略における最適化など、さまざまな目的のために変更する入力変数です。次に、グローバル空間でさらにいくつかの変数を定義します。
bool gridCycleRunning = false; double lastPositionLotSize, lastPositionPrice, priceLevelsSumation, totalLotSizeSummation;
これらの変数は以下の目的で使用されます。
- gridCycleRunning:サイクルが実行されていればtrue、そうでなければfalse(デフォルト:false)
- lastPositionLotSize:サイクルの任意の時点で最後に開いたポジションのロットサイズ(double)
- lastPositionPrice:サイクルの任意の時点における最後のポジションの始値(double)
- priceLevelsSumation:後に平均価格レベルを計算する際に使用される、すべてのポジションの未決済価格レベルの合計
- totalLotSizeSummation :後に平均価格レベルを計算する際に使用される、すべてのポジションのすべてのロットサイズの合計
//+------------------------------------------------------------------+ //| Hedge Cycle Intialization Function | //+------------------------------------------------------------------+ void StartGridCycle() { double initialPrice = initialPositionBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); ENUM_ORDER_TYPE positionType = initialPositionBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, 0, 0); lastPositionLotSize = initialLotSize; lastPositionPrice = initialPrice; ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10); ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed); priceLevelsSumation = initialLotSize * lastPositionPrice; totalLotSizeSummation = initialLotSize; if(m_trade.ResultRetcode() == 10009) gridCycleRunning = true; } //+------------------------------------------------------------------+StartGridCycle()関数では、まずdouble変数initialPriceを作成し、initialPositionBuy変数に応じて、ask価格またはbid価格を格納します。具体的には、initialPositionBuyがtrueの場合、ask価格を格納し、falseの場合、bid価格を格納します。買いポジションはask値、売りポジションはbidで建てる必要があるため、この区別は非常に重要です。
ENUM_ORDER_TYPE positionType = initialPositionBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; CTrade m_trade; m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, 0, 0);
ここで、initialPositionBuyの値に基づいて買いまたは売りのポジションを建てます。このために、ENUM_ORDER_TYPE型の変数positionTypeを作成します。ENUM_ORDER_TYPEはMQL5で定義済みのカスタム変数型で、0~8の整数値を取ることができます。以下の表に、定義されている注文タイプを表します。
整数値 | 識別子 |
---|---|
0 | ORDER_TYPE_BUY |
1 | ORDER_TYPE_SELL |
2 | ORDER_TYPE_BUY_LIMIT |
3 | ORDER_TYPE_SELL_LIMIT |
4 | ORDER_TYPE_BUY_STOP |
5 | ORDER_TYPE_SELL_STOP |
6 | ORDER_TYPE_BUY_STOP_LIMIT |
7 | ORDER_TYPE_SELL_STOP_LIMIT |
8 | ORDER_TYPE_CLOSE_BY |
実際、ENUM_ORDER_TYPE内では、値0と1はそれぞれORDER_TYPE_BUYとORDER_TYPE_SELLに対応します。ここでは、この2つに焦点を当てます。識別子はより直観的で覚えやすいため、整数値ではなく識別子を使用する方が有利です。
したがって、initialPositionBuyがtrueの場合はpositionTypeをORDER_TYPE_BUYに設定し、そうでない場合はORDER_TYPE_SELLに設定します。
まず、標準的な取引ライブラリTrade.mqhをグローバル空間にインポートします。これは、以下の文を使用しておこなわれます。
#include <Trade/Trade.mqh>
CTrade m_trade;
続けて、CTradeクラスのインスタンスも定義します。このインスタンスは取引操作の管理に使用されます。ポジションを建てるには、m_tradeのPositionOpen関数を使用します。この関数は、positionTypeやその他の関連する取引設定など、設定したパラメータに基づいて、買いまたは売りのポジションを実際に建てる処理をおこないます。
m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, 0, 0);
m_tradeインスタンスからPositionOpen関数を使用する際、ポジションを建てるのに不可欠ないくつかのパラメータがあります。
-
_Symbol:現在の取引銘柄
-
positionType:先ほどinitialPositionBuyの値に基づいて定義したもので、建てたポジションが買いか売りかを示す
-
initialLotSize:入力変数initialLotSizeで定義されたポジションのロットサイズ
-
initialPrice:ポジションが開かれる価格(オープンするポジションのタイプに応じて、ask価格またはbid価格を保持する、initialPrice変数によって決定)
-
SL(ストップロス)とTP(テイクプロフィット):最後の2つ。この特定の戦略では、注文の決済は戦略ロジックによって、特に価格が平均価格にtakeProfitの値を加えた価格に達したときに決定されるため、初期設定されない
コードの次の部分に移ります。
lastPositionLotSize = initialLotSize; lastPositionPrice = initialPrice; ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10); ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed);
-
lastPositionLotSize変数とlastPositionPrice変数の設定
- グローバル空間で定義された変数lastPositionLotSizeをinitialLotSizeと等しくなるように初期化します。この変数は、直近に建てたポジションのロットサイズを記録し、入力された乗数と乗じることで、次の注文のロットサイズを計算できるようにするため、非常に重要です。
- 同様に、lastPositionPriceもinitialPriceに設定されます。この変数もグローバル空間で定義され、後続の注文が発注される価格レベルを決定するために不可欠です。
-
視覚化のための水平線の作成
- チャート上で戦略を視覚的に表現するために、ObjectCreate関数を使用します。この関数には以下のパラメータが与えられます。
- 0:現在のチャート
- Next Position Price:オブジェクト名
- オブジェクトのタイプ:OBJ_HLINE
- 水平線に必要なのは価格レベルだけなので、サブウィンドウ用と日時用の2つの0を追加
- 次の注文のために計算された価格レベルを最終パラメータとする(lastPositionPrice - distance * _Point * 10)
- この水平線の色を赤に設定するには、OBJPROP_COLORプロパティをclrRedに設定してObjectSetInteger関数を使用します。
- チャート上で戦略を視覚的に表現するために、ObjectCreate関数を使用します。この関数には以下のパラメータが与えられます。
-
グリッドサイクルにおける平均価格管理の推進
- これらの初期ステップが完了したら、次はグリッドサイクル内の平均価格を管理します。これは、新しいポジションが建てられ、市場が発展するにつれて、平均価格を動的に計算し、調整することであり、グリッド取引戦略の重要な要素です。
コードの実装の次の部分に進みます。
priceLevelsSumation = initialLotSize * lastPositionPrice; totalLotSizeSummation = initialLotSize;
-
priceLevelsSumationの設定
- priceLevelsSumationをグローバル空間に定義し、すべての未決済ポジションの加重平均価格を計算します。最初は、注文が1つしかないので、priceLevelsSumationをlastPositionPriceに対応する重み(注文のロットサイズ)を乗じたものに設定します。この設定で、新しいポジションが建てられると、それぞれのロットサイズを乗じたより多くの価格レベルが蓄積されるように変数を準備します。
-
totalLotSizeSummationの初期化
- totalLotSizeSummation変数は、初期状態ではinitialLotSizeに等しく設定されています。加重平均の計算式では、加重の合計で割る必要があるので、これは理にかなっています。最初は、たった1つの注文で、総重量はその1つの注文のロットサイズとなります。さらにポジションを建てると、その重み(ロットサイズ)がこの合計に追加され、合計重みが動的に更新されます。
次に、StartGridCycle()関数の最後の部分に進みます。
if(m_trade.ResultRetcode() == 10009) gridCycleRunning = true;hedgeCycleRunningの設定
- 変数hedgeCycleRunningは、ポジションが正常に建てられた後にのみtrueに設定されます。これは、tradeというCTradeインスタンスからResultRetcode()関数を使用して検証されます。リターンコード10009は、発注が成功したことを示します(注:さまざまなリターンコードは、取引要求のさまざまな結果について参照することができる)。
- hedgeCycleRunningの使用は、グリッドサイクルの開始を示すため、戦略にとって極めて重要です。このフラグの意味は、この後のコードでより明らかになります。
グリッド戦略を開始するStartGridCycle()関数が完了したら、次はOnTick()関数に移ります。この機能を5つのセグメントに分け、それぞれが取引ロジックの特定の側面を処理します。
- グリッドサイクルの開始
- 買いポジションの処理
- 売りポジションの処理
- グリッドサイクル終了関数
- ポジション終了の処理
- グリッドサイクルの開始
double price = initialPositionBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); if(!gridCycleRunning) { StartGridCycle(); }
-
価格可変の宣言
- 新しい変数priceを宣言します。その値はinitialPositionBuyフラグによって決定されます。
- initialPositionBuyがtrueの場合、価格は現在のASK価格に設定されます。
- initialPositionBuyがfalseの場合、価格は現在のBID価格に設定されます。
- 新しい変数priceを宣言します。その値はinitialPositionBuyフラグによって決定されます。
-
gridCycleRunningに基づく条件付き実行
- 次のステップでは、gridCycleRunning変数を条件付きで確認します。
- gridCycleRunningがfalseの場合、グリッドサイクルがまだ開始されていないか、前のサイクルが完了しています。この場合、先に徹底的に説明したStartGridCycle()関数を実行します。この関数は、最初のポジションを建て、必要なパラメータを設定することにより、グリッドサイクルを初期化します。
- gridCycleRunningがtrueの場合、グリッドサイクルがすでにアクティブであることを意味します。このシナリオでは、当面は何もしないことを選択します。この決定により、既存のグリッドサイクルは、新しいサイクルを開始したり、現在のサイクルを妨害したりすることなく、確立されたロジックに基づいて操作を継続することができます。
- 次のステップでは、gridCycleRunning変数を条件付きで確認します。
このアプローチは、グリッドサイクルの開始と継続を効率的に管理し、取引戦略が設計された操作のフローを遵守することを保証します。グリッド戦略導入の次のステップに進みましょう。
-
- 買いポジションの処理
if(initialPositionBuy && price <= lastPositionPrice - distance * _Point * 10 && gridCycleRunning) { double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2); m_trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, newPositionLotSize, price, 0, 0); lastPositionLotSize *= lotSizeMultiplier; lastPositionPrice = price; ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10); ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed); priceLevelsSumation += newPositionLotSize * lastPositionPrice; totalLotSizeSummation += newPositionLotSize; ObjectCreate(0, "Average Price", OBJ_HLINE, 0, 0, priceLevelsSumation / totalLotSizeSummation); ObjectSetInteger(0, "Average Price", OBJPROP_COLOR, clrGreen); }
ここでは、3つの条件を持つ別のif文があります。
-
initialPositionBuyの条件
- このコードセグメントは、変数initialPositionBuyがtrueのときのみ実行されます。この条件により、買いポジションを処理するロジックと売りポジションを処理するロジックが分離されます。
- このコードセグメントは、変数initialPositionBuyがtrueのときのみ実行されます。この条件により、買いポジションを処理するロジックと売りポジションを処理するロジックが分離されます。
-
価格に関する条件
- 2つ目の条件は、変動価格がlastPositionPrice - distance * _Point * 10以下かどうかを確認します。この条件は、買いポジションを新規に建てるタイミングを決定する上で極めて重要です。ここでは、買いポジションのグリッド戦略で定義された方向性ロジックに合わせて、減算(-)操作が使用されます。
- 2つ目の条件は、変動価格がlastPositionPrice - distance * _Point * 10以下かどうかを確認します。この条件は、買いポジションを新規に建てるタイミングを決定する上で極めて重要です。ここでは、買いポジションのグリッド戦略で定義された方向性ロジックに合わせて、減算(-)操作が使用されます。
-
gridCycleRunningの条件
- 3つ目の条件は、変数gridCycleRunningがtrueであることで、グリッドサイクルが現在アクティブであることを示します。これは、新規ポジションが継続的な取引サイクルの一部としてのみ建てられることを保証するために不可欠です。
- 3つ目の条件は、変数gridCycleRunningがtrueであることで、グリッドサイクルが現在アクティブであることを示します。これは、新規ポジションが継続的な取引サイクルの一部としてのみ建てられることを保証するために不可欠です。
3つの条件がすべて満たされると、EAは新しい買いポジションを建てます。ただし、その前に新しいポジションのロットサイズを計算します。
- 新しいdouble変数newPositionLotSizeを宣言して、lastPositionLotSizeにlotSizeMultiplierを乗じた値に設定します。
- ロットサイズは0.01の厳密な倍数でなければならないので、ロットサイズを有効にするために、結果のロットサイズは小数点以下2桁に正規化されます。
この方法によって、グリッド戦略のルールを守りながら、新規ポジションを適切なサイズで建てることができます。その後、EAはm_tradeというCTradeクラスインスタンス(グローバル空間で先に宣言)のPositionOpen()関数を使用して、計算されたロットサイズで買いポジションを建てます。
ロジックを続けると、次のステップではlastPositionLotSize変数を更新します。これは、後続の注文のロットサイズの精度を維持するために極めて重要です。
- lastPositionLotSizeは、それ自体にlotSizeMultiplierを乗じたものに設定されます。重要なのは、この乗算には小数点以下2桁への正規化が含まれていないことです。
- 正規化を避ける理由は、lotSizeMultiplierが1.5でinitialLotSizeが0.01のシナリオを考えることで説明できます。0.01に1.5を乗じると0.015となり、これを小数点以下2桁に正規化すると0.01に四捨五入されます。このため、乗算されるにもかかわらず、ロットサイズが常に0.01のままというループが発生します。
- この問題を回避し、意図したとおりにロットサイズが増加するように、lastPositionLotSizeは、それ自体とlotSizeMultiplierの非正規化積を使用して更新されます。このステップは、グリッド戦略が正しく機能するために非常に重要であり、特に分数乗数を使用する場合に重要です。
この更新により、EAは新規ポジションのロットサイズを正確に追跡調整し、グリッド戦略の意図した進行を維持します。
続けて、次のステップでは、lastPositionPriceを更新し、次のポジションレベルをチャート上に表示します。
- lastPositionPriceの更新
- 変数lastPositionPriceは、initialPositionBuy条件によって決定された価格と等しく更新されます。if文の最初の条件は、initialPositionBuyがtrueのときのみエントリを保証するので、価格はこの文脈ではASK価格に対応します。
- 変数lastPositionPriceは、initialPositionBuy条件によって決定された価格と等しく更新されます。if文の最初の条件は、initialPositionBuyがtrueのときのみエントリを保証するので、価格はこの文脈ではASK価格に対応します。
- 次のポジションレベルの可視化
- チャート上に「Next Position Price」と名付けられた水平線を引きます。この線は、次の注文が発注される価格レベルを表します。
- initialPositionBuyがtrueで、次のポジションが買いであることを示すので、この行の価格レベルは、lastPositionPrice(ちょうど更新されたもの)から_Pointを乗じた距離(入力で指定されたもの)を差し引き、さらに10を乗じたものに設定されます。
- この水平線は、ObjectCreate()関数を使用して作成され、簡単に視覚化できるように、追加のオブジェクトプロパティ関数を使用してその色がclrRedに設定されています。この視覚的な支援は、次の買い注文の可能性のある価格レベルを簡単に特定するのに役立ちます。
lastPositionPriceを更新し、次の注文レベルを視覚的にマークすることで、EAはグリッドサイクルの後続ステップに効果的に備え、各新規ポジションが戦略の基準に合致し、チャート上で視覚的に追跡できるようにします。
ここで、平均価格レベルの計算を、特に加重平均に焦点を当てて、より洗練させてみましょう。
-
priceLevelsSummationとtotalLotSizeSummationの更新
- 加重平均価格を更新するには、lastPositionPriceとnewPositionLotSizeの積をpriceLevelsSummationに加算します。
- totalLotSizeSummationについては、単純にnewPositionLotSizeの値を加算します。
- これらの更新は、すべてのポジションの累積価格レベルと合計ロットサイズを追跡するために非常に重要です。
-
加重平均価格レベルの算出
- 平均価格レベルは、ここでは加重平均であり、priceLevelsSummationをtotalLotSizeSummationで割って算出されます。
- この計算は、それぞれのロットサイズを考慮し、すべての未決済ポジションの平均価格を正確に反映します。
-
加重平均価格レベルの視覚化
- ObjectCreate()関数を使用して、計算された平均価格レベルにもう1本の水平線をチャート上に作成します。
- この線の色はclrGreenに設定され、次のポジション価格を示す他の水平線と区別されます。
- この計算には、lastPositionLotSizeにlotSizeMultiplierを乗じた正規化値が使用されることに注意することが重要です。これにより、新しく建てたポジションの実際のロットサイズが考慮され、正確な加重平均が得られます。
これらのステップを組み込むことで、EAはすべての未決済ポジションの平均価格レベルを追跡するだけでなく、チャート上に視覚的に表現します。これにより、グリッドサイクルの現状や市場の状況に基づいて、簡単にモニタリングや意思決定をおこなうことができます。
-
- 売りポジションの処理
if(!initialPositionBuy && price >= lastPositionPrice + distance * _Point * 10 && gridCycleRunning) { double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2); m_trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, newPositionLotSize, price, 0, 0); lastPositionLotSize *= lotSizeMultiplier; lastPositionPrice = price; ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice + distance * _Point * 10); ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed); priceLevelsSumation += newPositionLotSize * lastPositionPrice; totalLotSizeSummation += newPositionLotSize; ObjectCreate(0, "Average Price", OBJ_HLINE, 0, 0, priceLevelsSumation / totalLotSizeSummation); ObjectSetInteger(0, "Average Price", OBJPROP_COLOR, clrGreen); }
ここでも、3つの条件を持つif文があります。
-
initialPositionBuyの条件
- この部分は、initialPositionBuyがfalseのときのみ実行されます。この条件は、ロジックが買いポジションとは異なる売りポジションを特別に処理することを保証します。
- この部分は、initialPositionBuyがfalseのときのみ実行されます。この条件は、ロジックが買いポジションとは異なる売りポジションを特別に処理することを保証します。
-
価格に関する条件
- 2つ目の条件は、価格がlastPositionPrice + distance * _Point * 10以上かどうかを確認します。これは、新しい売りポジションを建てるタイミングを判断するために不可欠です。この場合、加算(+)が使われ、戦略の売りポジションの方向ロジックと一致します。
- 2つ目の条件は、価格がlastPositionPrice + distance * _Point * 10以上かどうかを確認します。これは、新しい売りポジションを建てるタイミングを判断するために不可欠です。この場合、加算(+)が使われ、戦略の売りポジションの方向ロジックと一致します。
-
gridCycleRunningの条件
- 3つ目の条件は、gridCycleRunningがtrueであることで、グリッドサイクルがアクティブに実行されていることを示します。これにより、新規ポジションは継続的な取引サイクルの一部としてのみ建てられます。
- 3つ目の条件は、gridCycleRunningがtrueであることで、グリッドサイクルがアクティブに実行されていることを示します。これにより、新規ポジションは継続的な取引サイクルの一部としてのみ建てられます。
3つの条件がすべて満たされると、EAは新しい売りポジションを建てます。
- double変数newPositionLotSizeが宣言され、lastPositionLotSizeにlotSizeMultiplierを乗じた値に設定されます。
- この新しいロットサイズは、ロットサイズが0.01の厳密な倍数でなければならないため、有効なロットサイズにするために小数点以下2桁まで正規化されます。
- 最後に、EAはCTradeクラスインスタンスm_trade(グローバル空間で先に宣言)のPositionOpen()関数を使用して、計算されたロットサイズで売りポジションを建てます。
このアプローチは、グリッド戦略のルールを守り、戦略の進行を維持しながら、新しい売りポジションが適切な間隔とサイズで建てられることを保証します。
売りポジションを処理する次のステップでは、lastPositionLotSize変数を正しく更新することに焦点を当てます。
- 変数lastPositionLotSizeは、それ自体にlotSizeMultiplierを乗じたものに更新されます。このステップは、グリッドサイクルにおけるロットサイズの進行を維持するために極めて重要です。
- 重要なのは、この乗算は小数点以下2桁への正規化を伴わないことです。この決定は、端数乗数の潜在的な問題を避けるために重要です。
- 例として、lotSizeMultiplierが1.5、initialLotSizeが0.01の場合、結果は0.015となります。これを小数点以下2桁に正規化すると、0.01に丸められます。このプロセスを繰り返すと、ロットサイズは永遠に0.01となり、ループが発生して戦略の意図が損なわれます。
- この問題を回避するために、lastPositionLotSizeは、それ自体とlotSizeMultiplierの正規化されていない積に設定されます。この方法は、特にロットサイズが端数となる乗数を扱う場合に、ロットサイズが適切に増加することを保証します。
正規化せずにlastPositionLotSizeを更新することで、EAは新規売りポジションのロットサイズを効果的に追跡調整し、グリッド戦略が意図したとおりに機能することを保証します。
-
lastPositionPriceの更新
- 変数lastPositionPriceは、initialPositionBuyの値に応じて、ASK価格またはBID価格となる価格に更新されます。
- この場合、initialPositionBuyが(最初の条件に従って)falseである場合にのみ、コードのこの部分に入るので、lastPositionPriceはBID価格に設定されます。
-
次のポジションに水平線を引く
- チャート上に「Next Position Price」という水平線が引かれます。この線は、次の注文(この場合は売り注文)が発注される価格レベルを示します。
- この水平線の価格レベルは、lastPositionPrice(更新済み)に入力で指定された距離を加え、_Pointを乗じ、さらに10を乗じたものに設定されます。この計算によって、次の売り注文の適切なレベルが決定されます。
- 線は、チャート上にオブジェクトを描画するためのMQL5の定義済み関数であるObjectCreate()関数を使用して作成されます。
- この線の色はclrRedに設定され、視認性を高め、チャート上で区別しやすくしています。
lastPositionPriceを適切に設定し、次の注文のレベルを視覚的に表現することで、EAは後続の売り注文を効果的に準備し、それらがグリッド戦略のルールに合致し、チャート上で容易に識別できるようにします。
平均価格レベルの計算、特に売りポジションの加重平均に焦点を当てた処理では、以下のようなプロセスがあります。
-
priceLevelsSummationとtotalLotSizeSummationの更新
- lastPositionPriceにnewPositionLotSizeを乗じた値がpriceLevelsSummationに加算されます。このステップでは、すべての未決済ポジションの価格レベルを、それぞれのロットサイズによって加重平均して累積します。
- newPositionLotSizeの値がtotalLotSizeSummationに加算されます。この変数は、すべてのポジションの累積ロットサイズを追跡します。
-
加重平均価格レベルの算出
- 平均価格レベルは、priceLevelsSummationをtotalLotSizeSummationで割って得られます。この計算により、すべての未決済ポジションの加重平均価格が得られます。
- 平均価格レベルは、priceLevelsSummationをtotalLotSizeSummationで割って得られます。この計算により、すべての未決済ポジションの加重平均価格が得られます。
-
加重平均価格レベルの視覚化
- 水平線は、ObjectCreate()関数を使用して加重平均価格レベルで作成されます。この視覚的な表示は、すべてのポジションの平均価格を監視するのに役立ちます。
- この線の色はclrGreenに設定され、チャート上の他の線と容易に区別できるようになっています。
- これらの計算には、lastPositionLotSizeにlotSizeMultiplierを乗じた正規化値が使用されることに注意することが重要です。これにより、建てたポジションの実際の実際のロットサイズが考慮され、正確な加重平均計算がおこなわれます。
加重平均価格レベルを計算し、視覚化するこの方法は、グリッドサイクルにおける売りポジションの効果的な管理にとって極めて重要であり、現在の市場とポジションの状況に基づいた情報に基づく意思決定を可能にします。
-
- グリッドサイクル終了関数
//+------------------------------------------------------------------+ //| Stop Function for a particular Grid Cycle | //+------------------------------------------------------------------+ void StopGridCycle() { gridCycleRunning = false; ObjectDelete(0, "Next Position Price"); ObjectDelete(0, "Average Price"); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { m_trade.PositionClose(ticket); } } } //+------------------------------------------------------------------+
-
gridCycleRunningをFalseに設定する
- この関数の最初のアクションは、ブール変数gridCycleRunningをfalseに設定することです。この変化は、グリッドサイクルがもはやアクティブでなくなり、終了する過程にあることを意味します。
- この関数の最初のアクションは、ブール変数gridCycleRunningをfalseに設定することです。この変化は、グリッドサイクルがもはやアクティブでなくなり、終了する過程にあることを意味します。
-
チャートオブジェクトの削除
- 次に、定義済みのObjectDelete()関数を使用して、チャートから「次のポジションの価格」と「平均価格」の特定の2つのオブジェクトを削除します。このステップでは、これらのマーカーがチャートから消去され、サイクルが終了し、これらの価格レベルがもはや関係ないことを示します。
- 次に、定義済みのObjectDelete()関数を使用して、チャートから「次のポジションの価格」と「平均価格」の特定の2つのオブジェクトを削除します。このステップでは、これらのマーカーがチャートから消去され、サイクルが終了し、これらの価格レベルがもはや関係ないことを示します。
-
全ポジションの決済
- その後、この関数はすべての未決済ポジションをループします。
- 各ポジションはチケット番号で個別に選択されます。
- ポジションが選択されると、m_tradeインスタンスからPositionClose()関数を使用して閉じられます。ポジションのチケット番号がこの関数のパラメータとして渡されます。
- このシステマティックなアプローチにより、グリッドサイクルの一部として建てられたポジションはすべて閉じられ、その特定サイクルの取引活動は事実上終了します。
グリッドサイクルクローズ機能は、以下のステップを踏むことで、すべての未決済ポジションを体系的かつ効率的に決済し、取引環境をリセットし、新しいグリッドサイクルに備えてEAを準備します。
-
- ポジション決済処理
if(gridCycleRunning) { if(initialPositionBuy && price >= (priceLevelsSumation / totalLotSizeSummation) + takeProfit * _Point * 10) StopGridCycle(); if(!initialPositionBuy && price <= (priceLevelsSumation / totalLotSizeSummation) - takeProfit * _Point * 10) StopGridCycle(); }
最後のセクションである「ポジション決済処理」では、注文のクローズ処理は、特定の条件下で、if文によって制御されます。
- アクションが実行されるための第一条件は、gridCycleRunningがtrueであることで、これはアクティブなグリッドサイクルを示します。
- この中で、initialPositionBuyの値に基づいて、さらに2つの条件確認があります。
- initialPositionBuyがtrueで、現在の価格が加重平均価格レベルよりtakeProfit pips(入力変数で定義)高い場合、StopGridCycle()関数が実行されます。
- 逆に、initialPositionBuyがfalseで、現在価格が加重平均価格レベルよりtakeProfit pips低い場合、StopGridCycle()関数が実行されます。
これらの条件により、グリッドサイクルは停止し、ポジションは加重平均価格レベルに対する指定された利食い基準に基づいて決済されます。これにより、クラシックグリッド戦略の自動化プロセスは終了します。
クラシックグリッド戦略のバックテスト
クラシックグリッド戦略の自動化が完了したので、次は実際のシナリオでそのパフォーマンスを評価する番です。
バックテストでは、以下の入力パラメータを使用します。
- initialPositionBuy:true
- distance :15 pips
- takeProfit:5 pips
- initialLotSize:0.01
- lotSizeMultiplier:2.0
テストはEURUSDペアで2023年11月1日から2023年12月22日までおこなわれる。レバレッジは1:500、保証金は10,000ドルからです。時間枠に関しては戦略とは無関係なので、結果に影響を与えることなく、どのような選択でも十分であることは注目に値します。このテストは、2ヶ月弱という比較的短い期間を対象としていますが、より長期にわたる潜在的な結果の代表サンプルとしての役割を果たすことを目的としています。
さて、このバックテストの結果を掘り下げてみましょう。
この結果はかなり良さそうです。バックテストの結果を見てみると、グラフの青と緑の線で表される興味深い傾向が見られます。それぞれの線が何を意味するのか、そしてその動きが戦略のパフォーマンスをどのように反映しているのかを説明しましょう。
-
青と緑の線について
- 青い線は口座残高、緑の線はエクイティを示します。
- 残高が増加し、エクイティが減少し、最終的には同じ点に収束するという顕著なパターンが観察されます。
-
残高変動についての説明:
- この戦略では、サイクルのすべての注文を同時にクローズし、理想的にはストレートに残高を増やすことになります。しかし、グラフは残高の増減パターンを示しており、さらなる説明が必要です。
- この変動は、クローズオーダーのわずかな遅れ(コンマ数秒)に起因します。注文がほぼ同時に終了しているにもかかわらず、グラフはバランスの取れた増分更新を捉えています。
- 当初は、収益性の高いポジションが先に決済されるため、残高が増加します。ポジションは、関数PositionsTotal()で示されるように、最後の(最も収益性の高い)ポジションから始まるループで決済されます。したがって、残高ラインの上方および短期間の下方への動きは無視することができ、代わりに正味の上昇トレンドに焦点を当てます。
-
エクイティラインの動き
- バランスに対応し、エクイティは当初落ち込み、その後上昇します。これは、利益が出ているポジションが先に決済され、回復する前に一時的にエクイティが減少するためです。
- 緑色のエクイティラインの動きは、青色のバランスラインと同様のロジックに従い、全体的にポジティブなトレンドという同じ結論に至る。
まとめると、注文の終了順序や若干の遅れによりグラフにわずかな変動が見られるものの、最終的な収束と残高とエクイティの両ラインの上昇により、包括的なトレンドは戦略の成功を示しています。
バックテスト結果は、クラシックグリッド戦略の収益性を実証しています。しかし、この戦略に内在する重大な制限を認識することが重要です。
この戦略では、収益に達する前に発生するドローダウンに耐えるために、多額の資金が必要になることが多い。不利な市場動向の中でも、マージンコールに直面したり、損失覚悟でポジションを決済せざるを得なくなったりすることなく、ポジションを維持できる能力は極めて重要です。
この制限に対処することは、本シリーズの次のパートで重要な焦点となり、この戦略の最適化テクニックを探ることになります。その目的は、効率性を高め、必要な保有資金を減らすことで、資金規模の異なるトレーダーにとって、より利用しやすく、リスクの少ない戦略にすることです。
結論
今回は、クラシックグリッド戦略の複雑さを掘り下げ、MQL5のEAを使用して自動化に成功しました。また、この戦略の初期の成果を検証し、その可能性と改善点を浮き彫りにした。
しかし、このような戦略を最適化するための旅はまだ終わっていない。このシリーズの今後のパートでは、戦略の微調整、特にリターンを最大化し、ドローダウンを最小化するために、distance、takeProfit、initialLotSize、lotSizeMultiplierなどの入力パラメータの最適値に磨きをかけることに焦点を当てます。これからの調査で興味深いのは、買いポジションから始めるか売りポジションから始めるかの有効性を判断することです。
次回以降の記事で、さらに貴重な洞察やテクニックを明らかにしていく予定です。私の記事を読んでいただきありがとうございます。あなたの取引とコーディングの追求において、知識と実践的な助けとなることを願っています。このシリーズの次のパートで取り上げてほしい具体的なトピックやアイデアがあれば、いつでもご提案ください。
ハッピーコーディング!ハッピートレード!
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/13906





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索