この連載は研究用です。 読み物として楽しんでもらえれば。番外編なので読まなくてよいw
前回のおさらい
第6回の③段階ヘッジ型は、ナンピンしながら5本目と7本目で反対の玉を足す型でした。上限の10本に届いたサイクルが翌日に止まり、1年5か月そのまま残りました。
番外編の3つ目は ④挟み直し型 です。これまでの型と違って、ロットを増やしません。
第7回:挟んで、負けた側を少しずつ削る
| 項目 | 設定 |
|---|---|
| 始め方 | 今の価格の 2.5ドル上に買いの逆指値、2.5ドル下に売りの逆指値(どちらも 0.10ロット) |
| 片方だけ約定 | そのまま走って、サイクルの損益が +50 USD(0.01ロット換算で +5ドル)になったら全部決済 |
| 両方約定 | 勝っている側が建値から 5ドル走ったら、勝った側を決済。その利益から 10 USD を残して払える分だけ、負けた側を 0.01ロット単位で一緒に決済する |
| 負けた側だけ残る | 反対向きの逆指値を、負けた玉と同じロットで、価格から 5ドル先に置く。価格が負けた玉に有利な方へ動いたら、逆指値も付いていく |
| 置き直した玉 | 約定して、勝っている側が5ドル走ったら、上と同じように決済して削る |

ロットは 0.10 にしています。負けた側を 0.01ロットずつ削るので、0.01ロットでは削る余地がないからです。ほかの回と比べやすいように、金額には 0.01ロット換算(10で割った値)を併記します。
結果:6回中5回はプラス、1回が最後まで終わらなかった
条件は第0回の表のとおりです(XAUUSD、2025-01-01〜2026-07-01、リアルティック、初期証拠金 50,000 USD)。
| 項目 | 第6回(段階ヘッジ型) | 第7回(挟み直し型)0.10ロット | 0.01ロット換算 |
|---|---|---|---|
| 1サイクル(始めてから全部決済まで) | 55回 | 6回 | |
| うちプラスで終わったサイクル | 54回(合計 +283 USD) | 5回(合計 +254 USD) | +25 USD |
| 決済した回数 | 138回(勝率 64%) | 482回(勝率 98%) | |
| 損益合計 | −10,055 USD | −4,071 USD | −407 USD |
| 最大DD(含み損込み) | −23,087 USD | −15,499 USD | −1,550 USD |
| 含み損込みの一番低いところ(初期証拠金から) | −22,808 USD(2026-01-29) | −4,559 USD(2026-06-15) | −456 USD |
| 同時に持った玉 | 最大12本・0.12ロット | 最大2本・0.20ロット | 0.02ロット |
| 試行回数 N | 28 | 29 | |
| DSR | 0.002(N=100 換算 0.000) | 0.000(N=100 換算 0.000) |

今回の図は、これまでと形が逆です。確定した残高(黒)は階段状に増えて、一時は 60,940 USD まで上がりました。含み損込み(灰色)は、ゆっくり下がり続けています。2本の線の差が、ずっと持ち続けた負け玉の含み損です。テスト終了で強制決済すると、残高は 45,929 USD に落ちました。
何が起きたか:1月6日の売り0.05ロット
最初の5回は、どれも +50 USD 前後で終わりました。6回目のサイクルは 2025-01-06 15:47 に始まります。
| 時点 | 起きたこと |
|---|---|
| 1月6日 15:47〜16:17 | 売りが約定して、すぐ買いも約定。売りが5ドル走って利食い(+50.50)、買いを 0.03ロット削る(−32.46) |
| 16:43〜16:51 | 残った買い 0.07ロットの下に売りを置き直し、約定。今度は買いが5ドル走って決済、売りを 0.02ロット削る |
| 16:51 以降 | 売り 0.05ロット(建値 2,623.57)が残る。その上5ドルに買いの逆指値 |
ここからGoldは上がり続けました。残った売りの上に置いた買いは、約定して5ドル走るたびに、毎回 +25 USD 前後で利食いされます。全期間で 467回、1回も負けずに合計 +12,106 USD です。黒い線が増え続けたのはこのためです。
ところが、売りは一度も削れませんでした。0.01ロット削るには、買いの利益(約 +25 USD)から 10 USD を残して、売り 0.01ロット分の含み損を払わなければいけません。売りの含み損が 0.01ロットあたり 15 USD を超えると(価格が建値から15ドル上がると)、もう1回も削れません。それが1月6日のうちに起きました。
買いの利食いは、売りの含み損が増える分を後ろから追いかけていただけです。5ドル走った分しか取らず、次の逆指値はまた5ドル上に置くので、上げ幅のすべては取りきれません。その差とスワップが、灰色の線をゆっくり下げていきました。
そして 2026-01-29 01:39、Goldが急騰した高値の 5,598.99 で、最後の買いが約定しました。直後にGoldは下げ、買いは5ドル走れません。売りも建値(2,623.57)から5ドル下まで戻ることはありません。売りと買いが 0.05ロットずつ、ロックされた状態のまま、テスト終了まで5か月止まりました。
| 強制決済した玉 | 損益 | うちスワップ |
|---|---|---|
| 売り 0.05(2,623.57) | −6,920 USD | +383 USD |
| 買い 0.05(5,598.99) | −7,962 USD | −512 USD |
| このサイクルの合計(途中の利食い込み) | −4,325 USD |
(スワップの向きと大きさは業者や時期で変わるので、ここは割り引いて見てください。)
途中の2025年5〜8月と11月に黒い線が平らなのも、同じ理由です。高値近くで約定した買いが、下げて5ドル走れないまま、次の上げまで待っていました。
第6回と比べると
0.01ロット換算の損益は −407 USD、含み損込みの一番低いところは初期証拠金から −456 USD で、これまでの回よりずっと浅く済んでいます。ロットを増やさない型なので、残った負け玉は最初のロットの半分(0.01ロット換算で 0.005ロット)でした。
ただ、形は同じです。2025年1月の上げの入り口で始まったサイクルが1本、最後まで終わらなかった。損の大きさを決めたのは、そのとき残った玉の量でした。
第7回のまとめ
- 挟み直し型は、勝った側の利益で負けた側を少しずつ削る。ロットは増えない
- 今回の期間では、2025年1月6日に残った売り0.05ロットが、初日のうちに削れない大きさの含み損になり、最後まで残った
- その売りを追いかける買いは467回すべて勝ち、確定した残高は 60,940 USD まで増えたが、含み損込みの資産は下がり続けた
- 最後は高値で入った買いと売りがロックされ、5か月止まった
- 勝率98%でも、残った1本の玉が損益を決める
次回
番外編の最後は、⑥ゾーン・リカバリー型です。2本の線の間を行き来するたびに、ロットを大きくして反対に建てていきます。
試行回数について
今回は N=29 です。第6回(N=28)の続きで数えています。
ソースコード
研究用です。リアル口座では使わないでください。ログ出力の hg_log.mqh は第1回と同じです。ファイル名の番号(HG10)は作った順の通し番号で、回の番号とはずれています。
HG10_Squeeze.mq5
//+------------------------------------------------------------------+
//| HG10_Squeeze.mq5 |
//| Hedged Grid series, chapter 9 (extra): type 4, bracket squeeze |
//| Start : buy stop at ask + InpHalfWidth and sell stop at |
//| bid - InpHalfWidth, both InpLot. |
//| Win : close everything when the cycle result (closed profit of |
//| this cycle + floating profit incl. swap) reaches |
//| InpCycleTarget (a single fill that runs InpTakeProfit). |
//| Hedged: when both sides are open and price runs InpTakeProfit |
//| beyond the winner's open price, close the winner and as |
//| much of the loser (0.01 steps) as keeps the two together |
//| at or above InpSqueezeGain. |
//| Squeeze: with only the loser left, keep an opposite stop of the |
//| same volume InpReBracket away from price, trailing it as |
//| price moves in the loser's favour. Lots never increase. |
//| 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.10; // Lot size (0.01 steps are needed for partial closes)
input double InpHalfWidth = 2.5; // Initial stops: distance from price (USD)
input double InpTakeProfit = 5.0; // Run beyond the winner's open price that triggers a close (USD)
input double InpReBracket = 5.0; // Distance of the re-bracket stop from price (USD)
input double InpCycleTarget = 50.0; // Close all when the cycle result reaches this (account currency)
input double InpSqueezeGain = 10.0; // Winner + closed part of the loser must reach this (account currency)
input double InpTrailMin = 0.5; // Move the re-bracket stop only when it improves by this (USD)
input long InpMagic = 260810; // Magic number
input string InpTag = "ch09"; // Log file tag
CTrade trade;
double bal0 = 0;
bool cycleOn = false, squeezed = false;
int OnInit()
{
trade.SetExpertMagicNumber(InpMagic);
trade.SetTypeFillingBySymbol(_Symbol);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
HGLog_Deinit(InpTag);
}
struct Leg { ulong ticket; double vol, price, pnl; };
// Our open positions: at most one buy and one sell (lots never add up)
int Scan(Leg &buy, Leg &sell, double &floating)
{
buy.ticket = 0; sell.ticket = 0; floating = 0;
int n = 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++;
double pnl = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
floating += pnl;
Leg l;
l.ticket = t; l.vol = PositionGetDouble(POSITION_VOLUME);
l.price = PositionGetDouble(POSITION_PRICE_OPEN); l.pnl = pnl;
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) buy = l; else sell = l;
}
return n;
}
// Our pending stops: returns the count, and the last one found
int Pending(ulong &ticket, double &price, double &vol, bool &isBuy)
{
int n = 0; ticket = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
ulong t = OrderGetTicket(i);
if(t == 0 || OrderGetString(ORDER_SYMBOL) != _Symbol
|| OrderGetInteger(ORDER_MAGIC) != InpMagic) continue;
n++;
ticket = t; price = OrderGetDouble(ORDER_PRICE_OPEN);
vol = OrderGetDouble(ORDER_VOLUME_CURRENT);
isBuy = OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_BUY_STOP;
}
return n;
}
void DeletePending()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
ulong t = OrderGetTicket(i);
if(t > 0 && OrderGetString(ORDER_SYMBOL) == _Symbol
&& OrderGetInteger(ORDER_MAGIC) == InpMagic) trade.OrderDelete(t);
}
}
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);
}
}
// Close the winner and the largest part of the loser that keeps the pair at or above InpSqueezeGain
void Squeeze(const Leg &win, const Leg &lose, const MqlTick &tick)
{
double perStep = -lose.pnl / lose.vol * 0.01; // loss of 0.01 lot of the loser
int steps = 0;
if(perStep > 0) steps = (int)MathFloor((win.pnl - InpSqueezeGain) / perStep);
int maxSteps = (int)MathRound(lose.vol / 0.01);
steps = MathMax(0, MathMin(steps, maxSteps));
trade.PositionClose(win.ticket);
if(steps >= maxSteps) trade.PositionClose(lose.ticket);
else if(steps > 0) trade.PositionClosePartial(lose.ticket, NormalizeDouble(steps * 0.01, 2));
PrintFormat("HG10 squeeze win=%.2f loser %.2f lot pnl=%.2f closed %d steps sp=%.2f",
win.pnl, lose.vol, lose.pnl, steps, tick.ask - tick.bid);
squeezed = true;
}
void OnTick()
{
HGLog_OnTick();
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) return;
Leg buy, sell;
double floating;
int n = Scan(buy, sell, floating);
ulong pt; double pp, pv; bool pBuy;
int np = Pending(pt, pp, pv, pBuy);
if(cycleOn && n > 0 && AccountInfoDouble(ACCOUNT_BALANCE) - bal0 + floating >= InpCycleTarget)
{
CloseAll(); DeletePending(); cycleOn = false;
return;
}
if(n == 0)
{
if(cycleOn && np > 0 && !squeezed) return; // waiting for the first fill
// New cycle (also when a squeeze closed the loser completely)
DeletePending();
bal0 = AccountInfoDouble(ACCOUNT_BALANCE); squeezed = false;
string c = StringFormat("HG10 bracket sp=%.2f", tick.ask - tick.bid);
bool ok1 = trade.BuyStop(InpLot, NormalizeDouble(tick.ask + InpHalfWidth, _Digits), _Symbol, 0, 0, ORDER_TIME_GTC, 0, c);
bool ok2 = trade.SellStop(InpLot, NormalizeDouble(tick.bid - InpHalfWidth, _Digits), _Symbol, 0, 0, ORDER_TIME_GTC, 0, c);
cycleOn = ok1 && ok2;
if(!cycleOn) DeletePending();
return;
}
if(buy.ticket > 0 && sell.ticket > 0)
{
if(np > 0) DeletePending();
if(tick.bid >= buy.price + InpTakeProfit) Squeeze(buy, sell, tick);
else if(tick.ask <= sell.price - InpTakeProfit) Squeeze(sell, buy, tick);
return;
}
// One position left
if(!squeezed) return; // first fill: the other initial stop stays
bool loserIsBuy = buy.ticket > 0;
double vol = loserIsBuy ? buy.vol : sell.vol;
string c = StringFormat("HG10 rebracket sp=%.2f", tick.ask - tick.bid);
if(np > 0 && (MathAbs(pv - vol) > 1e-8 || pBuy == loserIsBuy)) { DeletePending(); np = 0; }
if(loserIsBuy)
{
double want = NormalizeDouble(tick.bid - InpReBracket, _Digits);
if(np == 0) trade.SellStop(vol, want, _Symbol, 0, 0, ORDER_TIME_GTC, 0, c);
else if(want > pp + InpTrailMin) trade.OrderModify(pt, want, 0, 0, ORDER_TIME_GTC, 0);
}
else
{
double want = NormalizeDouble(tick.ask + InpReBracket, _Digits);
if(np == 0) trade.BuyStop(vol, want, _Symbol, 0, 0, ORDER_TIME_GTC, 0, c);
else if(want < pp - InpTrailMin) trade.OrderModify(pt, want, 0, 0, ORDER_TIME_GTC, 0);
}
}

コメント