この連載は研究用です。 読み物として楽しんでもらえれば。
前回のおさらい
第2回は、逆行したら反対方向に同じ量を建てて(両建てロック)、建値に戻るのを待ちました。戻ったときの結果は「5ドル損切り+余計なコスト」。戻らなかったときは、1年5か月固まったままスワップを払い続けました。
固まったロックをほどくには、何かしら動きを足す必要があります。
第3回:抜けた方向に玉を足し、全体がプラスになったら全部決済
| 項目 | 設定 |
|---|---|
| エントリー・利食い・ロック | 第2回と同じ(ADXでエントリー、+5ドルで利食い、5ドル逆行したらロック) |
| 追加 | ロックしたあと、持っている玉の一番外側の建値から5ドル抜けたら、その方向に0.01ロットを1本足す(上に抜けたら買い、下に抜けたら売り) |
| まとめて決済 | 全部の玉の含み損益(スワップ込み)の合計が +5ドル になったら、全部決済 |
| 上限 | 20本まで |
たとえば、買いでエントリーして5ドル下がったら売りでロック。そこからさらに5ドル下がったら売りを1本追加、もう5ドル下がったらまた1本……。売りが増えるほど、下げが続けば全体はプラスに近づき、どこかで +5ドルに届いて全部決済できます。

トレンドが出ればロックがほどける仕組みです。弱点は右図のように、上下に往復されたときです。
結果:122勝1敗
条件は第0回の表のとおりです(XAUUSD、2025-01-01〜2026-07-01、リアルティック、0.01ロット、初期証拠金 50,000 USD)。
| 項目 | 値 |
|---|---|
| 1サイクル(エントリーから全部決済まで) | 123回 |
| うちプラスで終わったサイクル | 122回(99%、合計 +624 USD) |
| マイナスで終わったサイクル | 1回(−3,903 USD。テスト終了まで決済できず) |
| 損益合計 | −3,279 USD |
| 最大DD(含み損込み) | −6,414 USD(初期証拠金の12.8%) |
| 同時に持った玉 | 最大20本・0.20ロット |
| 試行回数 N | 19 |
| DSR | 0.002(N=100 換算 0.000) |

確定した残高(黒)は2025年4月まで順調に右肩上がりで、そこからはまったく動きません。含み損込みの線(灰色)は、4月下旬からじわじわ、Goldが上げ足を速めた9月以降は大きく沈んでいき、2026年1月末には 44,209 USD まで下がりました。最終日の段差は、テスト終了で全部の玉が強制的に閉じられた分です。
122勝1敗の1敗

2025年4月24〜25日のGoldは、3,320ドル前後でもみ合ったあと、25日の朝に3,365ドルまで上げ、昼から3,270ドルまで下げました。
- 上に抜けるたびに買いを足し、下に抜けるたびに売りを足す
- どちらかに抜け切る前に反対側へ戻されるので、全体の含み損益はプラスにならない
- 1日半で上限の20本(買い9本・売り11本)に到達して、それ以上何もできなくなった
これが、いわゆる往復ビンタです。抜けた方向に足す仕組みは、一方向のトレンドには強いのですが、幅の広いレンジを行ったり来たりされると、玉が増えるだけになります。
20本で止まったあと、Goldは4,000ドル台まで上がりました。売りが2本多いので、上がるほど含み損が増えます。スワップも毎日つきます。売り11本は受け取り(+668 USD)でしたが、買い9本の支払い(−2,673 USD)のほうがずっと大きく、差し引き14か月で −2,005 USD。最後はテスト終了の時点で強制的に決済して −3,903 USD でした。
ほかにも2回、20本に達したサイクルがありました(2月と4月上旬)。どちらも、その後トレンドが出て運よくプラスで決済できましたが、2月のほうは決済まで48日かかっています。
(ついでに言うと、25日の日本時間朝7時ごろ(Goldの取引が毎日再開する直後)に追加した買いの1本は、スプレッドが20ドル開いた瞬間に約定していました。再開直後はスプレッドが大きく開くので、こういうところで玉を足すのも損のもとです。)
第3回のまとめ
- 抜けた方向に足してまとめて決済すると、勝率は99%まで上がる。資産曲線も、途中まではきれいな右肩上がり
- そのかわり、負けるときは50回分の利益を一度に吹き飛ばす
- 1回の負けの中身は、往復ビンタで玉が上限まで積み上がり、そのまま固まってスワップを払い続けたもの
Xで見かける「チャートに矢印がずらっと並んで、資産曲線がきれい」なスクショは、たぶんこの黒い線の前半の部分です。線が多いほどインパクトはありますが、だいたいうまく行っていないときです。
次回
剥がし型には、この「積み上がった玉」を処理するための仕組みがもう1つあります。新しく大きめのロットで玉を建て(サブ玉)、その稼ぎで一番負けている玉を1本ずつ消していく剥がし(ピール)です。次回はこれを足して、固まった20本がほどけるかを見ます。
試行回数について
今回は N=19 です。
ソースコード
研究用です。これまた一応載せておく。ログ出力の hg_log.mqh は第1回と同じです。ファイル名の番号(HG04)は作った順の通し番号で、回の番号とはずれています。
HG04_Basket.mq5
//+------------------------------------------------------------------+
//| HG04_Basket.mq5 |
//| Hedged Grid series, chapter 3: lock, then add in the breakout |
//| direction and close the whole basket at a profit target. |
//| Entry : same as HG01 (ADX on M1 and M30, DI direction agrees) |
//| Single: take profit on the first position; lock (same size, |
//| opposite side) after InpLockDistance against it |
//| Basket: when price goes InpAddDistance beyond the highest (lowest) |
//| open price, add one buy (sell). Close everything when the |
//| floating profit of all positions 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 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; // 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; // Position cap
input long InpMagic = 260804; // Magic number
input string InpTag = "ch04"; // Log file tag
CTrade trade;
int hAdxM1 = INVALID_HANDLE, hAdxHTF = INVALID_HANDLE;
datetime lastEntryBar = 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);
}
// 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;
}
// Scan our positions: count, floating profit (profit + swap), outermost open prices
int Scan(double &floating, double &hiOpen, double &loOpen, ulong &single)
{
int n = 0;
floating = 0; hiOpen = -DBL_MAX; loOpen = DBL_MAX; single = 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++;
single = t;
floating += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
double op = PositionGetDouble(POSITION_PRICE_OPEN);
hiOpen = MathMax(hiOpen, op);
loOpen = MathMin(loOpen, op);
}
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);
}
}
void OnTick()
{
HGLog_OnTick();
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) return;
string sp = StringFormat("sp=%.2f", tick.ask - tick.bid);
double floating, hiOpen, loOpen;
ulong single;
int n = Scan(floating, hiOpen, loOpen, single);
if(n == 1)
{
// Single position: take profit is on the order; lock after an adverse move
PositionSelectByTicket(single);
bool isBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
double open = PositionGetDouble(POSITION_PRICE_OPEN);
if(isBuy && tick.bid <= open - InpLockDistance)
{
trade.PositionModify(single, 0, 0); // basket rules take over
trade.Sell(InpLot, _Symbol, tick.bid, 0, 0, "HG04 lock sell " + sp);
}
if(!isBuy && tick.ask >= open + InpLockDistance)
{
trade.PositionModify(single, 0, 0);
trade.Buy(InpLot, _Symbol, tick.ask, 0, 0, "HG04 lock buy " + sp);
}
return;
}
if(n >= 2)
{
if(floating >= InpBasketTarget) { CloseAll(); return; }
if(n >= InpMaxPositions) return;
if(tick.ask >= hiOpen + InpAddDistance)
trade.Buy(InpLot, _Symbol, tick.ask, 0, 0, "HG04 add buy " + sp);
else if(tick.bid <= loOpen - InpAddDistance)
trade.Sell(InpLot, _Symbol, tick.bid, 0, 0, "HG04 add sell " + sp);
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;
bool ok;
if(side == 1)
ok = trade.Buy(InpLot, _Symbol, tick.ask, 0,
NormalizeDouble(tick.ask + InpTakeProfit, _Digits), "HG04 buy " + sp);
else
ok = trade.Sell(InpLot, _Symbol, tick.bid, 0,
NormalizeDouble(tick.bid - InpTakeProfit, _Digits), "HG04 sell " + sp);
if(ok) lastEntryBar = bar;
}

コメント