この連載は研究用です。 読み物として楽しんでもらえれば。
前回のおさらい
番外編の4つの型(第5〜8回)は、どれも同じ形で負けました。小さなサイクルを何十回も +5ドル前後で終えたあと、終わらないサイクルが1本来て、それが損益のほとんどを決めました。本編の剥がし型(第3・4回)も同じです。
| 回 | 型 | 損益合計 | 終わらなかったサイクルの始まり |
|---|---|---|---|
| 第4回 | ⑤剥がし型 | −10,009 USD | 2025-02-11 |
| 第5回 | ①同時両建て型 | −11,973 USD | 2025-02-03 |
| 第6回 | ③段階ヘッジ型 | −10,055 USD | 2025-01-20 |
| 第7回 | ④挟み直し型(0.01ロット換算) | −407 USD | 2025-01-06 |
| 第8回 | ⑥ゾーン・リカバリー型 | −50,032 USD(口座がなくなった) | 2025-01-20 |
それなら、終わらないサイクルを 自分で終わらせて しまえばどうなるか。今回はこれを試します。
第9回:−50ドルで全部やめて、また始める
EAは第4回の剥がし型(HG06)に、ルールを1つだけ足したものです。
| 項目 | 設定 |
|---|---|
| ほかの設定 | 第4回とまったく同じ(ADXでエントリー、+5ドルで利食い、5ドル逆行でロック、5ドル抜けで追加、上限20本、サブ玉、剥がし、まとめて +5ドルで決済) |
| 足したルール | そのサイクルの玉の含み損益(メイン玉+サブ玉、スワップ込み)が −50ドルまで下がったら、全部決済する |
| そのあと | 何もなかったように、次のエントリーを待つ(いつもどおり ADX で入る) |
−50ドルは、0.01ロットの玉が10本ぶん、5ドルずつ逆行したくらいの損です。第4回で終わらなかったサイクルの損(約 −10,000 USD)の200分の1で打ち切る、ということになります。
比べる相手は、第4回と同じ設定を、今回あらためて回し直したものです。テスターは今のスワップ値を過去にも当てはめるので、第4回の表とは少しずれます(損益合計 −10,009 → −9,976 USD)。
結果:固まらなくなったが、損は2倍になった
条件は第0回の表のとおりです(XAUUSD、2025-01-01〜2026-07-01、リアルティック、メイン玉は0.01ロット、初期証拠金 50,000 USD)。
| 項目 | 第4回(損切りなし) | 第9回(−50ドルで損切り+再エントリー) |
|---|---|---|
| 1サイクル(エントリーから全部決済まで) | 48回 | 5,304回 |
| うちプラスで終わったサイクル | 47回(合計 +241 USD) | 4,422回(合計 +23,945 USD) |
| 損切りしたサイクル | — | 882回(合計 −46,023 USD) |
| 損益合計 | −9,976 USD | −22,079 USD |
| 最大DD(含み損込み) | −19,038 USD | −22,177 USD |
| 含み損込みの一番低いところ | 31,203 USD(2026-01-29) | 27,903 USD(2026-06-30) |
| 同時に持った玉 | 最大25本・0.30ロット | 最大12本・0.14ロット |
| 試行回数 N | 20 | 31 |
| DSR | 0.001(N=100 換算 0.000) | 0.000(N=100 換算 0.000) |

損切りを入れると、サイクルは固まらなくなりました。第4回は2025年2月28日のあと新しい玉を1本も建てられませんでしたが、第9回は最後の日まで建て続け、サイクルは5,304回まで増えました。玉の数も最大12本で止まっています。
それでも、損益合計は第4回の2倍以上です。図の金色の線は、ほとんど上がらないまま、下がり続けました。
何が起きたか:1回の損切りが、勝ち10回分を消す
サイクルの勝ちと負けを並べると、こうなります。
| 回数 | 1回あたり | |
|---|---|---|
| プラスで終わったサイクル | 4,422回(83.4%) | +5.41 USD |
| 損切りしたサイクル | 882回(16.6%) | −52.18 USD |
1回の損切りは、勝ち約10回ぶんです。トントンにするには、勝率が 90.6% 必要でした。実際は 83.4% です。
損切りの882回のうち、825回は −50〜−55 USD で止まっています。残りの57回(合計 −3,994 USD)は、週末をまたいだり、価格が一気に動いたりして、−50 を通り過ぎてから決済されたものです。いちばん大きいのは、2026年6月12日の金曜に始まって、週明け15日の朝に −238 USD で決済されたサイクルでした。
損はどこへ行ったか
もう1つ、別の見方をします。第9回では、玉を 18,377本 建てました。1本建てるたびに、買値と売値の差(スプレッド)を1回払っています。
| 項目 | 金額 |
|---|---|
| スプレッド(建てたときの差 × ロット、合計) | 約 −20,500 USD |
| 手数料 | −1,111 USD |
| スワップ | −477 USD |
| コストの合計 | 約 −22,100 USD |
| 損益合計 | −22,079 USD |
損益合計は、ほぼコストの合計と同じです。言いかえると、値動きそのものからは、勝ちも負けもほとんど出ていません。損切りして入り直すたびに払ったスプレッドが、そのまま積み上がっただけです。
とくに2026年1〜3月は、Goldが大きく上下して、エントリーと損切りが一気に増えました。この3か月で建てた玉は8,261本、スプレッドの中央値は1.12ドルに広がり、スプレッドだけで約12,800 USD を払っています。図の金色の線が2026年2月に急に落ちているのは、このためです。
第4回の損は、固まったサイクル1本(−10,217 USD、うちスワップ −2,570 USD)でした。第9回は、それを882回の小さな損切りと、18,377回分のスプレッドに置きかえただけでした。
5ドル動いたあと、戻ってくるのか
両建てグリッドは、どの型も「逆に動いた価格は、いずれ戻ってくる」ことを当てにしています。ロックして待つのも、玉を足すのも、剥がすのも、戻ってくれば勝ち、戻ってこなければ固まる、という作りです。
そこで最後に、Goldの1分足で、それを直接数えてみました。同じ向きに5ドルずつ m 回動いたあと、先に5ドル戻るか、先にもう5ドル進むかです。比べる相手として、同じ期間の1分ごとの値動きを並べ替えて「前の動きを覚えていない」値動きを作り、同じように数えました。

| 1回 | 2回 | 3回 | 4回 | 5回 | 6回 | 7回 | 8回 | |
|---|---|---|---|---|---|---|---|---|
| 実際のGold | 0.503 | 0.516 | 0.526 | 0.535 | 0.542 | 0.527 | 0.530 | 0.495 |
| 並べ替えたもの | 0.504 | 0.505 | 0.494 | 0.509 | 0.503 | 0.489 | 0.493 | 0.483 |
実際のGoldは、同じ向きに3〜5回動いたあとだけ、並べ替えたものより少し戻りやすくなっています。戻る力はゼロではありません。
ただ、スプレッドを払うと話が変わります。往復のコストを 0.35ドル(この連載を始める前の調査で使った値)とすると、5ドル戻るほうに賭けてトントンになる確率は 0.535 です(点線)。これを超えたのは5回のときの 0.542 だけで、それも95%の幅(縦線)は点線をまたいでいます。今回のテストで実際に払ったスプレッドは中央値で 0.7ドル前後だったので、トントンの線は 0.57 まで上がり、どの回数でも届きません。
(この数え方は、パラメータを選んだり EA を変えたりしていない、ただの集計です。なので試行回数 N には数えていません。)
第9回のまとめ
- 終わらないサイクルを −50ドルで損切りすると、サイクルは固まらなくなった
- ただし損切りは勝ち約10回ぶんで、トントンに必要な勝率 90.6% に届かず、損益合計は第4回の2倍以上(−22,079 USD)になった
- 損益合計は、ほぼスプレッドと手数料とスワップの合計だった。固まったサイクル1本の損が、882回の損切りと18,377回分のスプレッドに置きかわっただけだった
- Goldの値動きは、5ドル動いたあと少しだけ戻りやすいが、その差はスプレッドとほぼ同じ大きさだった
次回
次回は、ここまで素朴に作ってきた部品を全部、少しずつ賢くした 完成版 を回します。タイトルは「完成版を回す」です。
試行回数について
今回は N=31 です。第8回(N=30)の続きで数えています。比べた第4回の回し直しは、第4回と同じ試行なので数えていません。
ソースコード
研究用です。リアル口座では使わないでください。ログ出力の hg_log.mqh は第1回と同じです。第4回の HG06_Recovery.mq5 に、損切りの数行(InpCycleStop)を足しただけです。ファイル名の番号(HG12)は作った順の通し番号で、回の番号とはずれています。
HG12_StopReentry.mq5
//+------------------------------------------------------------------+
//| HG12_StopReentry.mq5 |
//| Hedged Grid series, chapter 11: HG06 (chapter 4) plus one rule. |
//| Stop : when the open positions (main + sub) lose InpCycleStop |
//| in total, close them all. The next cycle starts as usual. |
//| Everything else is exactly HG06. |
//| Main : same as HG04 (entry, lock, breakout adds, |
//| basket close of everything at InpBasketTarget) |
//| Sub : once the main group has InpSubStart positions, take the |
//| middle of its highest and lowest open prices. When price |
//| moves InpSubDistance away from it, open InpSubLot in that |
//| direction, and add one more every InpSubDistance. |
//| Peel : when (worst main position + all sub positions) >= |
//| InpPeelTarget, close them all, then re-center. |
//| 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; // Main 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 InpTakeProfit = 5.0; // Take profit of a single position (USD)
input double InpLockDistance = 5.0; // Adverse move that triggers the lock (USD)
input double InpAddDistance = 5.0; // Main breakout distance beyond the outermost open price (USD)
input double InpBasketTarget = 5.0; // Close all when floating profit reaches this (USD)
input int InpMaxPositions = 20; // Main position cap
input int InpSubStart = 8; // Main positions needed before the sub group starts
input double InpSubLot = 0.02; // Sub lot size
input double InpSubDistance = 5.0; // Sub breakout distance from the center / between sub adds (USD)
input int InpSubMax = 5; // Sub position cap
input double InpPeelTarget = 1.0; // Peel when worst main + all sub reaches this (USD)
input double InpCycleStop = 50.0; // Close all when floating profit falls to minus this (USD)
input long InpMagic = 260812; // Magic number (main); sub uses InpMagic + 1
input string InpTag = "ch11"; // Log file tag
CTrade trade;
int hAdxM1 = INVALID_HANDLE, hAdxHTF = INVALID_HANDLE;
datetime lastEntryBar = 0;
bool subMode = false; // Sub group is active for the current cycle
double subCenter = 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.SetTypeFillingBySymbol(_Symbol);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
HGLog_Deinit(InpTag);
}
// Direction from one ADX handle on the last closed bar: 1=buy, -1=sell, 0=none
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;
}
bool Ours(const ulong t, const long magic)
{
return t > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol
&& PositionGetInteger(POSITION_MAGIC) == magic;
}
double Net(const ulong t)
{
PositionSelectByTicket(t);
return PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
}
// Scan one group: count, floating profit, outermost open prices, last ticket,
// worst position (lowest net) and the open price of the latest position
int Scan(const long magic, double &floating, double &hiOpen, double &loOpen,
ulong &last, ulong &worst, double &lastOpen)
{
int n = 0;
double worstNet = DBL_MAX;
datetime lastTime = 0;
floating = 0; hiOpen = -DBL_MAX; loOpen = DBL_MAX; last = 0; worst = 0; lastOpen = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong t = PositionGetTicket(i);
if(!Ours(t, magic)) continue;
n++;
last = t;
double net = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
floating += net;
if(net < worstNet) { worstNet = net; worst = t; }
double op = PositionGetDouble(POSITION_PRICE_OPEN);
hiOpen = MathMax(hiOpen, op);
loOpen = MathMin(loOpen, op);
datetime ot = (datetime)PositionGetInteger(POSITION_TIME);
if(ot >= lastTime) { lastTime = ot; lastOpen = op; }
}
return n;
}
void CloseGroup(const long magic)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong t = PositionGetTicket(i);
if(Ours(t, magic)) trade.PositionClose(t);
}
}
int SubSide()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong t = PositionGetTicket(i);
if(Ours(t, InpMagic + 1))
return PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY ? 1 : -1;
}
return 0;
}
void Open(const long magic, const int side, const double lot, const double tp,
const string what, const MqlTick &tick)
{
trade.SetExpertMagicNumber(magic);
string c = StringFormat("HG12 %s %s sp=%.2f", what, side == 1 ? "buy" : "sell", tick.ask - tick.bid);
if(side == 1) trade.Buy(lot, _Symbol, tick.ask, 0, tp, c);
else trade.Sell(lot, _Symbol, tick.bid, 0, tp, c);
}
void OnTick()
{
HGLog_OnTick();
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) return;
double f1, hi1, lo1, lastOpen1, f2, hi2, lo2, lastOpen2;
ulong last1, worst1, last2, worst2;
int n1 = Scan(InpMagic, f1, hi1, lo1, last1, worst1, lastOpen1);
int n2 = Scan(InpMagic + 1, f2, hi2, lo2, last2, worst2, lastOpen2);
if(n1 == 0 && n2 > 0) { CloseGroup(InpMagic + 1); return; } // sub never stays alone
if(n1 == 0) subMode = false;
if(n1 == 1 && n2 == 0)
{
// Single position: take profit is on the order; lock after an adverse move
PositionSelectByTicket(last1);
bool isBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
double open = PositionGetDouble(POSITION_PRICE_OPEN);
if(isBuy && tick.bid <= open - InpLockDistance)
{
trade.PositionModify(last1, 0, 0);
Open(InpMagic, -1, InpLot, 0, "lock", tick);
}
if(!isBuy && tick.ask >= open + InpLockDistance)
{
trade.PositionModify(last1, 0, 0);
Open(InpMagic, 1, InpLot, 0, "lock", tick);
}
return;
}
if(n1 >= 2 || n2 > 0)
{
// Basket: everything (main + sub) at the target
if(f1 + f2 >= InpBasketTarget)
{
CloseGroup(InpMagic + 1); CloseGroup(InpMagic);
subMode = false;
return;
}
// Stop: give up the cycle
if(f1 + f2 <= -InpCycleStop)
{
CloseGroup(InpMagic + 1); CloseGroup(InpMagic);
subMode = false;
return;
}
// Peel: worst main position paid for by the sub group
if(n2 > 0 && worst1 > 0 && Net(worst1) + f2 >= InpPeelTarget)
{
CloseGroup(InpMagic + 1);
trade.PositionClose(worst1);
n1 = Scan(InpMagic, f1, hi1, lo1, last1, worst1, lastOpen1);
subCenter = n1 > 0 ? (hi1 + lo1) / 2 : 0;
return;
}
// Sub group starts once the main group is large enough, and stays on until the cycle ends
if(!subMode && n1 >= InpSubStart) { subMode = true; subCenter = (hi1 + lo1) / 2; }
if(subMode)
{
if(n2 == 0)
{
if(tick.ask >= subCenter + InpSubDistance) Open(InpMagic + 1, 1, InpSubLot, 0, "sub", tick);
else if(tick.bid <= subCenter - InpSubDistance) Open(InpMagic + 1, -1, InpSubLot, 0, "sub", tick);
}
else if(n2 < InpSubMax)
{
int s = SubSide();
if(s == 1 && tick.ask >= lastOpen2 + InpSubDistance) Open(InpMagic + 1, 1, InpSubLot, 0, "sub add", tick);
else if(s == -1 && tick.bid <= lastOpen2 - InpSubDistance) Open(InpMagic + 1, -1, InpSubLot, 0, "sub add", tick);
}
}
// Main breakout adds, as in HG04
if(n1 >= 1 && n1 < InpMaxPositions)
{
if(tick.ask >= hi1 + InpAddDistance) Open(InpMagic, 1, InpLot, 0, "add", tick);
else if(tick.bid <= lo1 - InpAddDistance) Open(InpMagic, -1, InpLot, 0, "add", tick);
}
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;
double tp = side == 1 ? NormalizeDouble(tick.ask + InpTakeProfit, _Digits)
: NormalizeDouble(tick.bid - InpTakeProfit, _Digits);
Open(InpMagic, side, InpLot, tp, "entry", tick);
lastEntryBar = bar;
}

コメント