要点:エクイティカーブ・トレーディング(Equity Curve Trading, ECT)は、EAのオン/オフやポジションサイズを「自分の資金曲線(エクイティ)」で制御するメタ戦略です。勝ちやすい局面だけを抽出し、ドローダウンを浅くし、回復を早めることを目的とします。本稿は、初心者が今日から実装できるように、概念→設計→MQL4完全コード→バックテスト→チューニング→運用までを一気通貫で解説します。
この記事は売買助言ではありません。実運用前に必ずデモ検証・小ロット検証を行ってください。
1. エクイティカーブ・トレーディングとは何か
通常のEAは、相場から生成したシグナル(例:移動平均クロス、ブレイクアウト、RSI等)にもとづいて売買します。ECTはその一段上位のレイヤで、EAの結果(損益)から得られる資金曲線を監視し、「いま勝ち運のモードか」「負けモードか」を自分の成績で判断し、取引を制御します。
- フィルタ型:資金曲線が平滑線の上なら取引許可、下なら休止。
- スロットル型:好調ならロット増、低調ならロット減。
- ブレーカー型:連敗や一定のドローダウンで強制クールダウン。
発想は単純ですが、過剰取引や連敗の深掘りを抑え、破綻確率を下げる効果が期待できます。
2. 初心者でも分かる最小実装:移動平均フィルタ
最小構成は以下の3要素だけです。
- エクイティ系列:取引ごとに累積損益(または Balance/Equity)を記録。
- 平滑線:エクイティの単純移動平均(例:MA(50))。
- ルール:Equity > MA を満たすときだけ新規エントリーを許可。
この「好調時のみ取る」だけで、無駄なトレードが削られ、カーブの素直さが改善しやすくなります。
3. 設計の全体像:パラメータとデータの流れ
本稿で実装するEAは、ベース戦略(移動平均クロス)+ECTガードの二層構造です。
- ベース戦略:短期MAと長期MAのクロスで売買(初心者に分かりやすい)。
- ECTガード:エクイティMAフィルタ、Zスコア判定、日次ドローダウン制限を装備。
- サイズ制御:ATRを用いたボラティリティ正規化ロット。
- リスク管理:SL/TP、トレイリング、ブレークイーブン移動、1日の損失上限。
- 運用保護:スプレッド監視、ニュース時間帯の休止(手動/時刻ベース)。
4. まずは結果のイメージ:ECTで何が変わるか
典型例として、同一のベース戦略を「ECTなし」と「ECTあり」でバックテストすると、多くのケースで以下の傾向が見られます。
- 最大ドローダウンの縮小:エクイティMAを下抜く低調期を休止できるため。
- リカバリー時間の短縮:負け期間の新規が減るため山谷が浅くなる。
- トレード回数の減少:無駄打ちが減る。結果として手数料/スリッページの総量も削減。
- PFやSQNの改善:質の良いトレードの比率が上がる。
一方で、フルベット時の理論上の最大利益は削られる可能性があります。目的は「無駄な損失の抑制と生存性の向上」です。
5. 実装:MQL4 完全コード(ベース戦略+ECTガード)
以下は、USDJPY(5分足想定)で動作するデモEAです。ブローカー仕様(銘柄名サフィックス、最小ロット、ストップレベル等)に合わせて調整してください。
//+------------------------------------------------------------------+
//| ECT_MA_Guard_EA.mq4 |
//| Equity Curve Trading (ECT) Guard + MA Crossover Base Strategy |
//| For educational use. Test on demo first. |
//+------------------------------------------------------------------+
#property strict
// ---- 外部パラメータ
extern string Base__ = "=== Base Strategy (MA Cross) ===";
extern int FastMAPeriod = 20;
extern int SlowMAPeriod = 50;
extern int MaShift = 0;
extern string Risk__ = "=== Risk & Money ===";
extern double RiskPerTradePercent = 1.0; // 口座残高に対する%
extern double MaxDailyLossPercent = 3.0; // 1日の損失上限(%)
extern double StopATRMult = 2.5; // ATR倍率のSL
extern double TakeATRMult = 5.0; // ATR倍率のTP
extern int ATRPeriod = 14;
extern string ECT__ = "=== ECT Guard ===";
extern int EquityMAPeriod = 50; // エクイティ移動平均の期間
extern double ZScoreEntryThr = 0.0; // Zスコアでの追加フィルタ(0で無効)
extern int MinTradesForECT = 30; // ECT有効化の最小取引数(初期学習)
extern string Exec__ = "=== Execution ===";
extern int Magic = 20250905;
extern int Slippage = 3;
extern double MaxSpread = 2.5; // pips
extern bool UseTrailing= true;
extern double TrailATRMult= 1.5;
extern bool MoveToBE = true;
extern double BE_ATRMult = 1.0;
extern string Time__ = "=== Time Filter (Broker Time) ===";
extern bool UseTimeFilter = false;
extern int StartHour = 0;
extern int EndHour = 24;
// ---- 内部変数
double g_equityMA = 0.0;
double g_equity = 0.0;
double g_equityStd= 0.0;
int g_totalTrades = 0;
datetime g_day = 0;
double g_dailyPnL = 0.0;
// ---- ユーティリティ
double PipValue()
{
double p = MarketInfo(Symbol(), MODE_POINT);
if(Digits == 3 || Digits == 5) return p*10.0;
return p;
}
double GetSpreadPips()
{
return (MarketInfo(Symbol(), MODE_SPREAD) * PipValue())/PipValue();
}
bool TimeAllowed()
{
if(!UseTimeFilter) return true;
int h = TimeHour(TimeCurrent());
if(StartHour <= EndHour) return (h >= StartHour && h < EndHour);
// 24h wrap
return (h >= StartHour || h < EndHour);
}
// ---- ATR計算
double ATR(int period)
{
return iATR(Symbol(), PERIOD_CURRENT, period, 0);
}
// ---- MA クロスシグナル
int BaseSignal()
{
double fast = iMA(Symbol(), PERIOD_CURRENT, FastMAPeriod, MaShift, MODE_SMA, PRICE_CLOSE, 0);
double slow = iMA(Symbol(), PERIOD_CURRENT, SlowMAPeriod, MaShift, MODE_SMA, PRICE_CLOSE, 0);
double fast1= iMA(Symbol(), PERIOD_CURRENT, FastMAPeriod, MaShift, MODE_SMA, PRICE_CLOSE, 1);
double slow1= iMA(Symbol(), PERIOD_CURRENT, SlowMAPeriod, MaShift, MODE_SMA, PRICE_CLOSE, 1);
if(fast1 <= slow1 && fast > slow) return 1; // ゴールデンクロス → Buy
if(fast1 >= slow1 && fast < slow) return -1; // デッドクロス → Sell
return 0;
}
// ---- ロット計算(ATRでボラ正規化)
double CalcLots(double atr, double stopATRMult)
{
if(atr <= 0) return 0.01;
double stopPips = (stopATRMult * atr) / PipValue();
if(stopPips <= 0.0) stopPips = 10.0;
double riskMoney = AccountBalance() * (RiskPerTradePercent/100.0);
double lotPerPipValue = MarketInfo(Symbol(), MODE_TICKVALUE) / PipValue();
double lots = riskMoney / (stopPips * lotPerPipValue);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double lotStep= MarketInfo(Symbol(), MODE_LOTSTEP);
lots = MathMax(minLot, MathFloor(lots/lotStep)*lotStep);
return lots;
}
// ---- ECT: エクイティ系列の更新とMA/Zスコア算出
void UpdateECTMetrics()
{
// 1日損益トラッキング
datetime today = DateOfDay(TimeCurrent());
if(g_day != today){ g_day = today; g_dailyPnL = 0.0; }
double balance = AccountBalance();
double equity = AccountEquity();
g_equity = equity;
// トレード数
g_totalTrades = OrdersHistoryTotal();
// 簡易移動平均と標準偏差(過去N本の履歴ベースで簡易算出)
// ここでは履歴のクローズ損益から擬似的に系列を生成
int n = MathMin(EquityMAPeriod, g_totalTrades);
if(n <= 1){ g_equityMA = balance; g_equityStd = 0; return; }
double sum=0, sum2=0;
double eq = 0;
int counted = 0;
for(int i=0; i<OrdersHistoryTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber()==Magic)
{
eq += OrderProfit()+OrderSwap()+OrderCommission();
counted++;
sum += eq;
sum2 += eq*eq;
if(counted>=n) break;
}
}
}
if(counted <= 1){ g_equityMA = balance; g_equityStd=0; return; }
double mean = sum / counted;
double var = MathMax(0.0, (sum2/counted) - mean*mean);
g_equityMA = balance + mean; // バランスに履歴損益の平均を足した近似
g_equityStd = MathSqrt(var);
}
// ---- ECT許可判定
bool ECT_AllowsEntry()
{
if(g_totalTrades < MinTradesForECT) return true; // 初期学習期間は通す
bool aboveMA = (g_equity >= g_equityMA);
if(!aboveMA) return false;
if(ZScoreEntryThr > 0 && g_equityStd > 0)
{
double z = (g_equity - g_equityMA) / g_equityStd;
if(z < ZScoreEntryThr) return false;
}
return true;
}
// ---- 日次ドローダウン制限
bool DailyLossLimitHit()
{
// 当日履歴を集計
double todaysPnL = 0.0;
for(int i=0; i<OrdersHistoryTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
{
if(OrderSymbol()==Symbol() && OrderMagicNumber()==Magic)
{
datetime ct = OrderCloseTime();
if(TimeDay(ct)==TimeDay(TimeCurrent()) && TimeMonth(ct)==TimeMonth(TimeCurrent()) && TimeYear(ct)==TimeYear(TimeCurrent()))
{
todaysPnL += (OrderProfit()+OrderSwap()+OrderCommission());
}
}
}
}
double limit = - AccountBalance() * (MaxDailyLossPercent/100.0);
return (todaysPnL <= limit);
}
// ---- 既存ポジ管理:トレイリング/BE
void ManageOpenPositions(double atr)
{
for(int i=0; i<OrdersTotal(); i++)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=Magic) continue;
double point = MarketInfo(Symbol(), MODE_POINT);
double dist = TrailATRMult*atr;
// Move to BE
if(MoveToBE)
{
if(OrderType()==OP_BUY)
{
double be = OrderOpenPrice();
if(Bid - be >= BE_ATRMult*atr && OrderStopLoss() < be)
OrderModify(OrderTicket(), OrderOpenPrice(), be, OrderTakeProfit(), 0, clrBlue);
}
else if(OrderType()==OP_SELL)
{
double be = OrderOpenPrice();
if(be - Ask >= BE_ATRMult*atr && (OrderStopLoss()==0 || OrderStopLoss() > be))
OrderModify(OrderTicket(), OrderOpenPrice(), be, OrderTakeProfit(), 0, clrRed);
}
}
if(!UseTrailing) continue;
if(OrderType()==OP_BUY)
{
double newSL = Bid - dist;
if(newSL > OrderStopLoss())
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrBlue);
}
else if(OrderType()==OP_SELL)
{
double newSL = Ask + dist;
if(OrderStopLoss()==0 || newSL < OrderStopLoss())
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrRed);
}
}
}
// ---- エントリー
void TryEntry()
{
if(!TimeAllowed()) return;
if(DailyLossLimitHit()) return;
if(GetSpreadPips() > MaxSpread) return;
int sig = BaseSignal();
if(sig==0) return;
UpdateECTMetrics();
if(!ECT_AllowsEntry()) return;
double atr = ATR(ATRPeriod);
double lots= CalcLots(atr, StopATRMult);
if(lots <= 0) return;
double sl=0,tp=0;
double distSL = StopATRMult*atr;
double distTP = TakeATRMult*atr;
int slip = Slippage;
if(sig>0)
{
sl = Bid - distSL;
tp = Bid + distTP;
OrderSend(Symbol(), OP_BUY, lots, Ask, slip, sl, tp, "ECT Buy", Magic, 0, clrBlue);
}
else if(sig<0)
{
sl = Ask + distSL;
tp = Ask - distTP;
OrderSend(Symbol(), OP_SELL, lots, Bid, slip, sl, tp, "ECT Sell", Magic, 0, clrRed);
}
}
// ---- ライフサイクル
int OnInit(){ g_day=DateOfDay(TimeCurrent()); return(INIT_SUCCEEDED); }
int OnDeinit(){ return(0); }
void OnTick()
{
double atr = ATR(ATRPeriod);
ManageOpenPositions(atr);
TryEntry();
}
//+------------------------------------------------------------------+
注意:学習用の簡易ECTです。実運用ではジャーナル出力、例外処理、銘柄/口座仕様への適合、スプレッド単位の確認、サーバ時刻と夏時間差の調整など、堅牢化を行ってください。
6. バックテストのやり方(MT4)
- データ品質:可能なら高精度ティックデータ(90%超)。低品質だとECTのON/OFFタイミングが再現しにくくなります。
- 期間:最低3〜5年、ボラ急変期(コロナ、介入期等)を含める。
- 銘柄:USDJPYやEURUSDなどスプレッドが狭い主要通貨から。
- パラメータ:MA(20/50)やEquityMA(50)は起点。感度は後述のグリッドで最適化。
- 評価指標:最大DD、プロフィットファクター(PF)、勝率、SQN、MAR、Ulcer Indexなど。
- 分解:「ECTなし」と「ECTあり」を同条件で比較、差分の原因を特定。
7. 推奨チューニング手順
- ベース戦略の健全性確認:ECTは万能ではありません。ベースが完全に負け戦略なら休止が増えるだけです。
- EquityMAの期間:短すぎると頻繁にON/OFFし約定コストが増えます。長すぎると反応が鈍くなります(例:30〜100で探索)。
- Zスコア閾値:0〜1.0程度で微調整。過度に上げると機会損失が増えます。
- 日次損失上限:2〜5%の範囲で口座・心的耐性に合わせて設定。
- ロット算出:ATRに加え、口座余力や銘柄の最小ロット・ストップレベルを考慮。
8. 典型的な落とし穴
- 過学習:EquityMAやZ閾値を最適化しすぎると将来効かない。必ずアウトオブサンプルを用意してください。
- ブローカー差:スプレッド、小数桁、最小ストップ距離、約定仕様で結果が変わります。
- コスト軽視:トレード回数が減る一方、ON/OFFの切替で機会を逃すコストも生じます。
- 記録不足:日別損益、連敗数、フィルタ判定をログに残し、後から原因分析できるように。
9. バリエーション(上級)
- 二値→連続制御:MA上なら1.0、下なら0.0ではなく、距離やZスコアで0〜1の重みを与えロットを連続調整。
- レジーム連動:VIXや金利、セッション別ボラ等の外生変数とエクイティ情報を融合。
- 複数戦略のメタPF:各EAのエクイティを合成し、ポートフォリオ全体のオン/オフを制御。
- カーブフィット抑制:ウォークフォワード、クロスバリデーションをECT閾値にも適用。
10. 検証チェックリスト
- ECTなし/ありを同一条件で比較し、DD・PF・回復日数の差分を確認。
- EquityMA期間とZ閾値を粗グリッド→微調整の順で探索。
- エントリー許可・拒否の理由をログで追跡し、過度な拒否や偏りがないかを確認。
- 手動でも一時停止できるUI(グローバル変数やパネル)を備える。
11. よくある質問(FAQ)
Q1:完全に負け期を避けられますか?
A:いいえ。ECTは期待値を押し上げる魔法ではありません。損失の深掘りを抑え、生存性を高める補助輪です。
Q2:どの銘柄が向いていますか?
A:スプレッドが狭く、取引時間が長いメジャー通貨が初学者向けです。株価指数CFDや金も検証可能ですが、仕様差に注意してください。
Q3:最初からECTをONにすべき?
A:過去の履歴がないと判定が不安定です。最初の一定トレードまでは通し、十分な系列ができてからECTを厳格化する運用が無難です。
12. まとめ
ECTは、「自分の成績で自分を制御する」シンプルなメタ戦略です。勝ちやすいときに賭け、負けやすいときは退く——この規律が、長期の生存率を上げます。まずは本稿のEAをデモで動かし、ECTのオン/オフ差分を自分の目で確認してください。
コメント