両建てグリッドEAをゼロから作る(第6回・番外編:③段階ヘッジ型)

この連載は研究用です。 読み物として楽しんでもらえれば。番外編なので読まなくてよいw

前回のおさらい

第5回の①同時両建て型は、線に触れるたびに買いと売りを両方建てる型でした。勝った側をすぐ利食いするので、一方向に走ると負けた側だけが残り、含み損込みの資産は 5,925 USD まで沈みました。

番外編の2つ目は ③段階ヘッジ型 です。最初は片側だけで入り、ナンピンしていって、決まった本数になったら反対の玉を足します。

第6回:決まった本数で、反対の玉を足す

項目設定
最初の1本第1回と同じ ADX(1分足と30分足で ADX(14)≥22、DI の向きがそろったらその向き。0.01ロット)
足す最初の1本から 5ドル逆行するごとに、同じ向き・同じロットで1本。最大 10本
反対の玉同じ向きの玉が 5本目と 7本目になったところで、反対向きを1本ずつ(0.01ロット)
反対の玉の決済建てた線から 5ドル戻ったら決済(ほぼ −5ドル)。また線まで来たら建て直す
まとめて決済そのサイクルの決済済みの損益と、残っている玉の含み損益(スワップ込み)の合計が +5ドル以上で全部決済

第2回のロックや第4回のサブ玉と違うのは、反対の玉を「本数」で決めて足すところです。

段階ヘッジ型の動き

反対の玉は、逆行が続いている間は含み損の増え方を抑えます。でも、価格が戻るとその玉が損を出して決済され、また逆行すると建て直されます。 

結果:55回中54回はプラス、1回が最後まで終わらなかった

条件は第0回の表のとおりです(XAUUSD、2025-01-01〜2026-07-01、リアルティック、0.01ロット、初期証拠金 50,000 USD)。

項目第5回(同時両建て型)第6回(段階ヘッジ型)
1サイクル(始めてから全部決済まで)28回55回
うちプラスで終わったサイクル27回(合計 +142 USD)54回(合計 +283 USD)
決済した回数964回(勝率 89%)138回(勝率 64%)
損益合計−11,973 USD−10,055 USD
最大DD(含み損込み)−46,647 USD−23,087 USD
含み損込みの一番低いところ5,925 USD(2026-01-29)27,192 USD(2026-01-29)
同時に持った玉最大20本・0.20ロット最大12本・0.12ロット
試行回数 N2728
DSR0.006(N=100 換算 0.001)0.002(N=100 換算 0.000)
資産の推移

確定した残高(黒)は、最初の3週間で少し増えたあとは、テスト終了までほとんど動いていません。止まっていたからです。含み損込み(灰色)は2025年1月から下がり始め、2026年1月29日に 27,192 USD まで沈みました。テスト終了で残った玉を強制決済すると、残高は 50,265 USD から 39,945 USD に落ちました。 

第5回の図と形がよく似ていますが、別モノです。どちらも2025年の初めから売りの玉を抱えたまま最後まで終わらず、含み損込みの資産が「Goldの値動きの裏返し」になったので、同じ形になりました(一番低かった日も同じ2026年1月29日)。抱えていた売りの量が、第5回は差し引き0.20ロット、今回は0.08ロットだったので、振れ幅は第5回の半分以下です。縦軸の目盛りが違うので、見比べるときは注意してください。

何が起きたか:1月20日に始まって、21日に止まった

最初の3週間で54回のサイクルが +5ドル前後で終わりました。玉が9本まで増えたサイクルも2回ありましたが、どちらも戻ってきてプラスで終わっています。

55回目のサイクルは、2025-01-20 の深夜に 売り(2,693.57)で始まりました。ここからGoldが上がり始めます。

時点起きたこと
1月20日5ドルごとに売りを足し、5本目(2,713台)で反対の買いを1本
1月20〜21日価格が戻るたびに買いが −5.6〜−6.1ドルで決済され、上がるとまた建て直す
1月21日7本目で2本目の買い。売りは 2,738.57 で10本目に到達
1月21日 16:59最後の約定。ここから先、このサイクルでは何も起きない

10本の上限に届き、反対の買いも2本とも持ったままなので、新しく建てる条件も決済する条件も来ません。このまま1年5か月、テスト終了まで止まっていました。その間にGoldは 2,700ドル台から 4,000ドル前後まで上がっています。

最後に残っていたのは、売り10本と買い2本、差し引き 売り0.08ロット です。

強制決済した玉損益うちスワップ
売り10本−12,915 USD+736 USD
買い2本+2,575 USD−716 USD
このサイクルの合計−10,338 USD

(スワップの向きと大きさは業者や時期で変わるので、ここは割り引いて見てください。)

反対の買いは、全期間で14回建てて、途中で決済した12回の合計が −66 USD でした。最後まで残った2本は +2,575 USD を稼いでいますが、売り10本の損を埋めるには、本数が足りませんでした。 

第5回と比べると

含み損込みの一番低いところは、第5回の 5,925 USD に対して 27,192 USD。沈み方は半分ほどです。違いは本数の上限で、同じ向きの玉が10本、反対の玉が2本までなので、差し引きで持てる量は最大 0.08ロットに抑えられています。

ただ、損益合計はほぼ同じ −10,000 USD 前後です。そして、どちらも1月〜2月の上げの入り口で始まったサイクルが1本、最後まで終わらなかったことが損のほぼすべてです。止まった理由は違っても(第5回は上限20本の売り、今回は上限10本の売りと反対の買い2本)、結果の形は同じでした。 

第6回のまとめ

  • 段階ヘッジ型は、決まった本数(5本目と7本目)で反対の玉を足す。逆行中は含み損の増え方を抑えるが、戻ると反対の玉が損切りされる
  • 今回の期間では、2025年1月20日に売りで始まったサイクルが、翌日に上限の10本に届いて止まり、最後まで終わらなかった
  • 上限があるので、沈み方は第5回の半分ほど。でも損益合計はほぼ同じだった
  • 上限は損の深さを決めるが、損が出るかどうかは決めない

次回

番外編の3つ目は、④挟み直し型です。上下に逆指値を置いて価格を挟み、負けた側の玉を少しずつ削っていく型です。 

試行回数について

今回は N=28 です。第5回(N=27)の続きで数えています。 

ソースコード

研究用です。リアル口座では使わないでください。ログ出力の hg_log.mqh は第1回と同じです。ファイル名の番号(HG09)は作った順の通し番号で、回の番号とはずれています。

HG09_Staged.mq5

//+------------------------------------------------------------------+
//| HG09_Staged.mq5                                                   |
//| Hedged Grid series, chapter 8 (extra): type 3, staged hedge       |
//| Entry : same as HG01 (ADX on M1 and M30, DI direction agrees)     |
//| Adds  : every InpStep against the first position, add one more in |
//|         the same direction (same lot), up to InpMaxMain positions. |
//| Hedge : when the InpHedgeAt1-th (InpHedgeAt2-th) add level is hit,|
//|         open one opposite position of the same lot. Close it when |
//|         price comes back InpStep from its trigger level (it may    |
//|         open again later).                                        |
//| Exit  : close everything when the cycle result (closed profit of  |
//|         this cycle + floating profit incl. swap) reaches           |
//|         InpBasketTarget.                                          |
//| Research code for the fxwolf2.com blog. Not for live trading.     |
//+------------------------------------------------------------------+
#property copyright "fxwolf2.com"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>
#include "hg_log.mqh"

input double InpLot          = 0.01;   // Lot size
input int    InpAdxPeriodM1  = 14;     // ADX period (M1)
input double InpAdxLevelM1   = 22.0;   // ADX threshold (M1)
input int    InpAdxPeriodHTF = 14;     // ADX period (higher timeframe)
input double InpAdxLevelHTF  = 22.0;   // ADX threshold (higher timeframe)
input ENUM_TIMEFRAMES InpHTF = PERIOD_M30; // Higher timeframe
input double InpStep         = 5.0;    // Distance between adds (USD)
input int    InpMaxMain      = 10;     // Max positions in the entry direction
input int    InpHedgeAt1     = 5;      // First hedge at this many main positions
input int    InpHedgeAt2     = 7;      // Second hedge at this many main positions
input double InpBasketTarget = 5.0;    // Close all when the cycle result reaches this (USD)
input long   InpMagic        = 260809; // Magic number
input string InpTag          = "ch08"; // Log file tag

CTrade   trade;
int      hAdxM1 = INVALID_HANDLE, hAdxHTF = INVALID_HANDLE;
datetime lastEntryBar = 0;
int      dir = 0, nMain = 0;           // cycle direction (1=buy, -1=sell) and main positions opened
double   anchor = 0, bal0 = 0;
ulong    hedge[2] = {0, 0};

int OnInit()
{
   hAdxM1  = iADX(_Symbol, PERIOD_M1, InpAdxPeriodM1);
   hAdxHTF = iADX(_Symbol, InpHTF, InpAdxPeriodHTF);
   if(hAdxM1 == INVALID_HANDLE || hAdxHTF == INVALID_HANDLE) return INIT_FAILED;
   trade.SetExpertMagicNumber(InpMagic);
   trade.SetTypeFillingBySymbol(_Symbol);
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   HGLog_Deinit(InpTag);
}

int AdxSide(const int handle, const double level)
{
   double adx[1], plus[1], minus[1];
   if(CopyBuffer(handle, 0, 1, 1, adx)   != 1) return 0;
   if(CopyBuffer(handle, 1, 1, 1, plus)  != 1) return 0;
   if(CopyBuffer(handle, 2, 1, 1, minus) != 1) return 0;
   if(adx[0] < level) return 0;
   if(plus[0] > minus[0]) return 1;
   if(minus[0] > plus[0]) return -1;
   return 0;
}

int Scan(double &floating)
{
   int n = 0;
   floating = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong t = PositionGetTicket(i);
      if(t == 0 || PositionGetString(POSITION_SYMBOL) != _Symbol
         || PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;
      n++;
      floating += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
   }
   return n;
}

void CloseAll()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong t = PositionGetTicket(i);
      if(t > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol
         && PositionGetInteger(POSITION_MAGIC) == InpMagic) trade.PositionClose(t);
   }
}

bool Open(const int side, const MqlTick &tick, const string what)
{
   string c = StringFormat("HG09 %s sp=%.2f", what, tick.ask - tick.bid);
   if(side == 1) return trade.Buy(InpLot, _Symbol, tick.ask, 0, 0, c);
   return trade.Sell(InpLot, _Symbol, tick.bid, 0, 0, c);
}

void OnTick()
{
   HGLog_OnTick();
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol, tick)) return;
   double p = tick.bid;

   double floating;
   int n = Scan(floating);

   if(n > 0)
   {
      if(AccountInfoDouble(ACCOUNT_BALANCE) - bal0 + floating >= InpBasketTarget)
      {
         CloseAll();
         return;
      }
      // Adds in the entry direction, against the move
      if(nMain < InpMaxMain)
      {
         double lvl = anchor - dir * nMain * InpStep;
         if((dir == 1 && p <= lvl) || (dir == -1 && p >= lvl))
            if(Open(dir, tick, StringFormat("add%d", nMain + 1))) nMain++;
      }
      // Staged hedges
      int at[2];
      at[0] = InpHedgeAt1; at[1] = InpHedgeAt2;
      for(int i = 0; i < 2; i++)
      {
         double trig = anchor - dir * (at[i] - 1) * InpStep;
         if(hedge[i] == 0)
         {
            if(nMain >= at[i] && ((dir == 1 && p <= trig) || (dir == -1 && p >= trig)))
               if(Open(-dir, tick, StringFormat("hedge%d", i + 1))) hedge[i] = trade.ResultOrder();
         }
         else
         {
            double stop = trig + dir * InpStep;
            if((dir == 1 && p >= stop) || (dir == -1 && p <= stop))
               if(trade.PositionClose(hedge[i])) hedge[i] = 0;
         }
      }
      return;
   }

   // Flat: at most one new entry per M1 bar
   datetime bar = iTime(_Symbol, PERIOD_M1, 0);
   if(bar == lastEntryBar) return;
   int side = AdxSide(hAdxM1, InpAdxLevelM1);
   if(side == 0 || AdxSide(hAdxHTF, InpAdxLevelHTF) != side) return;

   bal0 = AccountInfoDouble(ACCOUNT_BALANCE);
   if(Open(side, tick, "entry"))
   {
      lastEntryBar = bar;
      dir = side; nMain = 1; hedge[0] = 0; hedge[1] = 0;
      anchor = p;
   }
}

コメント

タイトルとURLをコピーしました