両建てグリッドEAをゼロから作る(第5回・番外編:①同時両建て型)

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

番外編について

第4回までで、⑤剥がし型を部品から組み立てるところまで終わりました。ここからは番外編として、第0回で紹介した残りの型を、同じ条件で1つずつ回します。「いや、さっさと完成版出せよ」と言われそうですが、第10回で出す予定。

今回は、いちばん素直な ①同時両建て型 です。 

第5回:線に触れるたびに、買いと売りを両方建てる

項目設定
線の引き方サイクルを始めたときの価格から、上下に 5ドルごと
建てる価格が線に触れるたびに、買いと売りを1本ずつ(0.01ロット)。同じ線では、1サイクルにつき買い・売り各1回まで
利食いどの玉も、1本先の線(+5ドル)で決済
負けた側残す(損切りなし)
まとめて決済そのサイクルの決済済みの損益と、残っている玉の含み損益(スワップ込み)の合計が +5ドル以上で全部決済。次のティックから新しいサイクル
上限持っている玉が 20本のあいだは新しく建てない

この型には、最初の1本を決める ADX がありません。サイクルを始めた瞬間に、その場で買いと売りを1本ずつ建てます。

同時両建て型の2つの動き

左のように行って戻れば、買いも売りも +5ドルで利食いできます。右のように一方向に走ると、勝った側だけが次々に利食いされ、負けた側が1段ずつ重なっていきます。第0回の「損が2乗で増える」の図は、この右側を計算したものでした。 

結果:残高は増え続け、含み損込みでは88%減った

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

項目第4回(剥がし型)第5回(同時両建て型)
1サイクル(始めてから全部決済まで)48回28回
うちプラスで終わったサイクル47回27回(合計 +142 USD)
決済した回数–964回(勝率 89%)
損益合計−10,009 USD−11,973 USD
最大DD(含み損込み)−19,061 USD(38%)−46,647 USD
含み損込みの一番低いところ31,179 USD5,925 USD(2026-01-29)
同時に持った玉最大24本・0.28ロット最大20本・0.20ロット
試行回数 N2027
DSR0.0010.006(N=100 換算 0.001)
資産の推移

確定した残高(黒)は、テスト終了の直前までほぼ一度も下がらずに増え続けています。最後は 53,104 USD。決済した玉だけを見れば、勝率89%の「勝ち続けるEA」です。

ところが含み損込み(灰色)は、2025年9月から沈み始め、2026年1月29日には 5,925 USD まで下がりました。初期証拠金の88%が含み損で消えていたことになります。テスト終了で残った玉を強制決済すると、黒い線は一気に 38,027 USD まで落ちました。 

何が起きたか:2月3日の売り10本

最初の1か月は順調でした。2月3日の朝までに27回のサイクルが、どれも +5ドル前後で終わっています。ところが28回目のサイクルは 2025-02-03 の朝に始まり、テスト終了まで1年5か月終わりませんでした。

この日、Goldは2,770ドル台から2,830ドル台まで上がりました。線を上に抜けるたびに買いと売りが建ち、買いは次の線で利食い、売りは残ります。図の右側そのままの動きで、1日半で売りが10本たまりました。

その後も同じことが繰り返され、テスト終了時に残っていたのは、20本すべて売りでした。

建てた時期本数建値
2025-02-03〜0410本2,772〜2,832
2025-03-131本2,957
2025-09-01〜298本3,457〜3,792
2026-06-301本3,947

テスト終了時のGoldは 4,000ドル前後です。強制決済した売り20本の損益は −16,201 USD でした。

このサイクルの中でも、決済は662回あり、合計 +3,020 USD を稼いでいます(買い375回・売り287回)。上がる途中の買いと、ときどき下げたときの売りが、+5ドルずつ利食いされた分です。黒い線が増え続けていたのはこのためです。でも、それより速く、残った売りの含み損が増えていきました。

  • 上限の20本に届くと、新しい玉は建たない。売りが1本利食いされて19本になると、また線に触れたところで建てる
  • 残った売りには損切りがない。まとめて決済は「サイクル全体で +5ドル」にならないと動かない
  • Goldは1年5か月、大きくは戻らずに上がり続けた

(売りの玉には、このテスト口座ではプラスのスワップが付いていました。20本で +1,107 USD です。スワップの向きと大きさは業者や時期で変わるので、ここは割り引いて見てください。) 

もちろん、延命措置はいろいろあるものの、今回は掘りません。

第4回と比べると

損益合計は、第4回の剥がし型(−10,009 USD)とほぼ同じ水準です。違うのは途中の深さで、含み損込みの一番低いところは 31,179 USD と 5,925 USD。同時両建て型は、勝った側を毎回すぐ利食いしてしまうので、手元に残るのはいつも負けている側の玉だけになります。トレンドが続けば、20本の上限いっぱいまで同じ向きの負け玉がそろいます。 

第5回のまとめ

  • 同時両建て型は、往復する相場では両方を利食いできるが、一方向に走ると負けた側だけが残る
  • 今回の期間では、2025年2月3日に始まったサイクルが最後まで終わらず、残ったのは売り20本だった
  • 確定した残高は勝率89%で増え続けたが、含み損込みでは初期証拠金の88%を失った時期があった
  • 確定した残高だけを見ていると、この沈み方は見えない 

次回

番外編の2つ目は、③段階ヘッジ型です。同じロットでナンピンしていき、決まった本数で反対の玉を足します。

 

 

 

試行回数について

今回は N=27 です。第4回(N=20)のあと、記事にしなかった試行が6回(N=21〜26:最初の1本を ADX 以外にしたもの5回と、設定値を大きく変えたもの1回)あるので、その続きで数えています。

 

 

 

ソースコード

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

HG08_Simultaneous.mq5

//+------------------------------------------------------------------+
//| HG08_Simultaneous.mq5                                             |
//| Hedged Grid series, chapter 7 (extra): type 1, simultaneous hedge |
//| Grid  : lines every InpStep from the price at the cycle start.    |
//|         Each time price reaches a line, open one buy and one sell  |
//|         (each side at most once per line per cycle).               |
//| Exit  : every position has TP one line away; the loser is kept.    |
//|         Close everything when the cycle result (closed profit of   |
//|         this cycle + floating profit incl. swap) reaches            |
//|         InpBasketTarget. A new cycle starts on the next tick.       |
//| Cap   : no new positions while InpMaxPositions are open.           |
//| 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 double InpStep         = 5.0;    // Grid step and take profit (USD)
input double InpBasketTarget = 5.0;    // Close all when the cycle result reaches this (USD)
input int    InpMaxPositions = 20;     // Position cap
input long   InpMagic        = 260808; // Magic number
input string InpTag          = "ch07"; // Log file tag

CTrade trade;
double anchor = 0, bal0 = 0, prevBid = 0;
int    occBuy[], occSell[];            // grid lines already used in this cycle

int OnInit()
{
   trade.SetExpertMagicNumber(InpMagic);
   trade.SetTypeFillingBySymbol(_Symbol);
   return INIT_SUCCEEDED;
}

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

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 Used(int &arr[], const int k)
{
   for(int i = 0; i < ArraySize(arr); i++) if(arr[i] == k) return true;
   return false;
}

void Mark(int &arr[], const int k)
{
   int n = ArraySize(arr);
   ArrayResize(arr, n + 1);
   arr[n] = k;
}

// Open the missing sides on grid line k. Returns the number of positions opened.
int OpenLine(const int k, const MqlTick &tick, int n)
{
   double L = anchor + k * InpStep;
   string sp = StringFormat("sp=%.2f k=%d", tick.ask - tick.bid, k);
   int opened = 0;
   if(n < InpMaxPositions && !Used(occBuy, k))
   {
      if(trade.Buy(InpLot, _Symbol, tick.ask, 0, NormalizeDouble(L + InpStep, _Digits), "HG08 buy " + sp))
      { Mark(occBuy, k); opened++; n++; }
   }
   if(n < InpMaxPositions && !Used(occSell, k))
   {
      if(trade.Sell(InpLot, _Symbol, tick.bid, 0, NormalizeDouble(L - InpStep, _Digits), "HG08 sell " + sp))
      { Mark(occSell, k); opened++; }
   }
   return opened;
}

void OnTick()
{
   HGLog_OnTick();
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol, tick)) return;

   double floating;
   int n = Scan(floating);

   if(n == 0)
   {
      // New cycle: the grid is anchored at the current bid
      anchor = tick.bid; bal0 = AccountInfoDouble(ACCOUNT_BALANCE); prevBid = tick.bid;
      ArrayResize(occBuy, 0); ArrayResize(occSell, 0);
      OpenLine(0, tick, 0);
      return;
   }

   if(AccountInfoDouble(ACCOUNT_BALANCE) - bal0 + floating >= InpBasketTarget)
   {
      CloseAll();
      return;
   }

   // Grid lines crossed by the bid since the last tick
   double a = anchor;
   if(tick.bid > prevBid)
   {
      for(int k = (int)MathFloor((prevBid - a) / InpStep) + 1; k <= (int)MathFloor((tick.bid - a) / InpStep); k++)
         n += OpenLine(k, tick, n);
   }
   else if(tick.bid < prevBid)
   {
      for(int k = (int)MathCeil((prevBid - a) / InpStep) - 1; k >= (int)MathCeil((tick.bid - a) / InpStep); k--)
         n += OpenLine(k, tick, n);
   }
   prevBid = tick.bid;
}

コメント

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