この連載は研究用です。 読み物として楽しんでもらえれば。番外編なので読まなくてよいw
番外編について
第4回までで、⑤剥がし型を部品から組み立てるところまで終わりました。ここからは番外編として、第0回で紹介した残りの型を、同じ条件で1つずつ回します。「いや、さっさと完成版出せよ」と言われそうですが、第10回で出す予定。
今回は、いちばん素直な ①同時両建て型 です。
第5回:線に触れるたびに、買いと売りを両方建てる
| 項目 | 設定 |
|---|---|
| 線の引き方 | サイクルを始めたときの価格から、上下に 5ドルごと |
| 建てる | 価格が線に触れるたびに、買いと売りを1本ずつ(0.01ロット)。同じ線では、1サイクルにつき買い・売り各1回まで |
| 利食い | どの玉も、1本先の線(+5ドル)で決済 |
| 負けた側 | 残す(損切りなし) |
| まとめて決済 | そのサイクルの決済済みの損益と、残っている玉の含み損益(スワップ込み)の合計が +5ドル以上で全部決済。次のティックから新しいサイクル |
| 上限 | 持っている玉が 20本のあいだは新しく建てない |
この型には、最初の1本を決める ADX がありません。サイクルを始めた瞬間に、その場で買いと売りを1本ずつ建てます。

左のように行って戻れば、買いも売りも +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 USD | 5,925 USD(2026-01-29) |
| 同時に持った玉 | 最大24本・0.28ロット | 最大20本・0.20ロット |
| 試行回数 N | 20 | 27 |
| DSR | 0.001 | 0.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〜04 | 10本 | 2,772〜2,832 |
| 2025-03-13 | 1本 | 2,957 |
| 2025-09-01〜29 | 8本 | 3,457〜3,792 |
| 2026-06-30 | 1本 | 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;
}

コメント