「高勝率のスキャルピングEAは、どうやって作ればいいのか?」
この記事では、実際にMT4用のスキャルピングEAを設計し、バックテストしながら改善した流れを、初級~中級者向けにできるだけわかりやすく紹介します。
今回作成したのは、USDJPYの5分足を対象にしたAIスキャルピングEA V1です。
EMAで大きな方向を確認し、ストキャスティクスで押し目・戻りを探し、ATRで値動きが極端な場面を避けるシンプルな構成にしています。
今回の最終結果
2025年通年・USDJPY M5・全ティック・スプレッド5pointsで検証した結果、
698取引 / 勝率85.39% / PF1.18 / 最大DD2.89%となりました。
ナンピン・マーチンは使わず、1回の取引リスクは口座残高の0.5%を基準にしています。
EAそのものの基本構成や「そもそもEAをどう作るのか」から確認したい方は、先に基礎記事を読んでおくと理解しやすくなります。
スキャルピングEAの作り方|まずロジックを決める
EAを作るときは、いきなりMQL4のコードを書くよりも、まず「どんな場面で買う・売るのか」を整理した方が作りやすくなります。
今回のEAでは、単純にストキャスティクスが売られすぎ・買われすぎになっただけで逆張りするのではなく、大きなトレンド方向に対する押し目・戻りを狙う形にしました。
| 役割 | 使用する指標 | 今回の設定 |
|---|---|---|
| トレンド方向を確認 | EMA | EMA50 / EMA200 |
| 押し目・戻りを探す | Stochastic | 5,3,3 / Buy 17 / Sell 90 |
| 値動きの大きさを確認 | ATR | ATR14 / 2.5~12pips |
| 利確・損切り | 固定TP / SL | TP 5pips / SL 25pips |
| 資金管理 | 残高連動ロット | 1回の損失リスク0.5% |
買いエントリーの条件
買いは、EMA50がEMA200より上にある上昇方向で、ストキャスティクスがかなり低い位置まで下がったあとに上向きへクロスした場面を狙います。
EMA50 > EMA200
ストキャスティクスのKが17未満
前の足ではKがD以下、直近確定足でKがDを上抜け
ATRが2.5~12pipsの範囲内
上昇トレンド中でも、価格が一時的に下がる場面はあります。そこでストキャスティクスを使い、「上昇方向の中で、かなり深く押したところ」だけを拾うイメージです。
売りエントリーの条件
売りは反対で、EMA50がEMA200より下にある下降方向で、ストキャスティクスが高い位置まで戻したあとに下向きへクロスした場面を狙います。
EMA50 < EMA200
ストキャスティクスのKが90超
前の足ではKがD以上、直近確定足でKがDを下抜け
ATRが2.5~12pipsの範囲内
買い側が17、売り側が90と左右対称ではありません。これは後述するバックテストで、売り側はかなり深く引きつけた方が成績が安定したためです。
決済はTP5pips・SL25pips
利確は5pips、損切りは25pipsです。数字だけを見ると損大利小ですが、高勝率型のスキャルピングEAではこのような設計になることがあります。
ただし、利確幅を小さくして損切りを大きくするだけでは勝てません。今回の検証でも、SLだけを変更した段階ではPF1を超えませんでした。
バックテストの「総利益 ÷ 総損失」で計算される指標です。
PF1.00を超えるとテスト上は利益が損失を上回り、数値が高いほど利益側が大きいことを示します。
2025年通年の最終バックテスト結果
まずは、完成したV1の2025年通年バックテスト結果を先に確認します。
| 項目 | 結果 |
|---|---|
| 通貨ペア | USDJPY |
| 時間足 | M5(5分足) |
| 期間 | 2025年1月~12月 |
| モデル | 全ティック |
| スプレッド | 5points |
| 初期証拠金 | 1,000,000円 |
| 1回のリスク | 口座残高の0.5% |
| 総取引数 | 698回 |
| 勝率 | 85.39% |
| PF | 1.18 |
| 純益 | +92,113.30円 |
| 最大DD | 2.89% |

勝率は85%を超えましたが、PFは1.18です。「高勝率だから非常に強いEA」という見方ではなく、勝率と損益の両方を見ることが重要です。
また、最大DDは2.89%でした。今回は1回のSL到達時に口座残高のおよそ0.5%を失うようロットを自動計算しています。

ここからは、この結果になるまでにどこを見直したのかを、要点だけ紹介します。
最初から勝てたわけではない|バックテストしながら改善
今回のEAも、最初からPF1.18になったわけではありません。初期版は勝率こそ高かったものの、PF0.87でトータルはマイナスでした。
そこで、まずSLを15~30pipsまで変えて確認しましたが、PFは0.86~0.91の範囲で、どの設定でもPF1を超えませんでした。
この結果から、損切り幅ではなくエントリー条件そのものを見直す必要があると判断しました。
| 見直したポイント | 結果 | 判断 |
|---|---|---|
| 初期ロジック | PF0.87 | 高勝率でもマイナス |
| SLを15~30pipsで比較 | PF0.86~0.91 | SL調整だけでは改善せず |
| エントリー位置を厳選 | 開発期間PF1.20 | Buy17 / Sell90を採用 |
特に効果があったのは、ストキャスティクスでより深い押し目・戻りまで待つようにしたことです。最終的に開発期間ではBuyLevel=17 / SellLevel=90でPF1.20まで改善しました。
逆に、EMA50の傾きまで条件に追加すると取引数が減り、PFも悪化したため不採用にしています。条件を増やせば成績が良くなるわけではないという点も、EA作りでは重要です。
数字が悪いときにパラメータを闇雲に増やすのではなく、どの条件が結果に影響しているのかを一つずつ確認します。
今回の場合は、SL調整よりもエントリーを厳選する方が効果的でした。
調整に使っていない期間でも確認する
バックテストで注意したいのが、調整した期間だけ良く見えるEAを作ってしまうことです。
そこで今回は2025年をすべて調整に使わず、1~9月をロジック調整用、10~12月を確認用に分けました。
パラメータ調整やロジック改善に使っていない「未使用期間」で成績を確認する方法です。
本記事では2025年10~12月をOOS期間とし、この期間の結果を確認したあとに成績を良くするための再調整は行っていません。
| 期間 | 用途 | 取引数 | 勝率 | PF |
|---|---|---|---|---|
| 2025年1~9月 | ロジック調整 | 542 | 85.61% | 1.20 |
| 2025年10~12月 | 未使用期間で確認 | 156 | 84.62% | 1.11 |
未使用期間ではPFが1.20から1.11へ下がりましたが、勝率は84.62%を維持し、トータルでもプラスでした。
ここでPF1.20へ戻すために10~12月の結果を見ながら再調整すると、せっかく分けた未使用期間まで調整データになってしまいます。そのため、追加最適化は行わず、そのままV1の最終仕様としました。
スプレッドが広がった場合も確認
スキャルピングEAは利確幅が小さいため、スプレッドの影響を受けやすいです。
そこで、ロジックを変えずにスプレッドだけ3・5・10pointsへ変更して確認しました。
| スプレッド | 取引数 | 勝率 | PF |
|---|---|---|---|
| 3points | 700 | 85.43% | 1.18 |
| 5points | 698 | 85.39% | 1.18 |
| 10points | 692 | 83.67% | 1.04 |
3~5pointsではほぼ同じ成績でしたが、10pointsになるとPFは1.04まで低下しました。
TPが5pipsしかないEAなので、スプレッドが広がるほど不利になります。今回のV1では最大スプレッド5pointsを初期設定としています。
AIスキャルピングEA V1のMQL4ソースコード
以下が今回作成したV1のソースコードです。MT4用のMQL4コードで、EMA・ストキャスティクス・ATR、スプレッド制限、リスクベースのロット計算まで含めています。
このEAはバックテスト結果を紹介する学習・検証用サンプルです。将来の利益を保証するものではありません。
利用する場合は、まずデモ口座やバックテストで動作を確認し、FX業者ごとの最小注文距離・スプレッド・取引条件も確認してください。
EAファイルをダウンロード
用途に合わせてEX4・MQ4の2種類を用意しています。
まず動かしてみたい方はEX4、ソースを確認・編集したい方はMQ4をご利用ください。
※以下のソースコードは、上でダウンロードできるMQ4版と同一内容です。EX4版は、このMQ4をコンパイルした実行ファイルです。
//+------------------------------------------------------------------+
//| AI_HighWin_Scalper_v1.00.mq4 |
//| 2026 High-Win-Rate Scalping EA - Final Verified Version |
//| https://fx-prog.com/ |
//+------------------------------------------------------------------+
#property strict
#property version "1.00"
#property description "USDJPY M5 high-win-rate scalper - verified 2025 backtest version"
#property description "EMA trend + Stochastic pullback + ATR volatility filter"
#property description "No martingale / no averaging down / one position per symbol+magic"
//--- Verified baseline
// USDJPY / M5 / 2025 full year / Every tick / spread 5 points
// EMA50/EMA200 trend filter
// Stochastic(5,3,3): BuyLevel=17 / SellLevel=90
// ATR(14): 2.5 - 12.0 pips
// TP=5 pips / SL=25 pips / Risk=0.5% of balance
// No martingale / no averaging down / one position per symbol+magic
//
// 2025 reference result (FXTF-Demo):
// 698 trades / win rate 85.39% / PF 1.18 / max DD 2.89%
// OOS 2025-10-01 to 2025-12-31: win rate 84.62% / PF 1.11
// Backtest results do not guarantee future performance.
//--- Signal settings
input ENUM_TIMEFRAMES SignalTimeframe = PERIOD_M5;
//--- Trend filter
input int FastEMAPeriod = 50;
input int SlowEMAPeriod = 200;
//--- Pullback / reversal timing
input int StochKPeriod = 5;
input int StochDPeriod = 3;
input int StochSlowing = 3;
input int BuyLevel = 17;
input int SellLevel = 90;
//--- Volatility filter
input int ATRPeriod = 14;
input double ATRMinPips = 2.5;
input double ATRMaxPips = 12.0;
//--- Exit settings
input double TakeProfitPips = 5.0;
input double StopLossPips = 25.0;
//--- Execution / risk settings
input int MaxSpreadPoints = 5;
input double BrokerMinDistancePips = 5.0; // FXTF: minimum TP/SL distance
input double RiskPercent = 0.5;
input int SlippagePoints = 20;
input int MagicNumber = 26082410;
//--- Internal state
datetime g_lastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization |
//+------------------------------------------------------------------+
int OnInit()
{
if(FastEMAPeriod <= 0 ||
SlowEMAPeriod <= FastEMAPeriod ||
StochKPeriod <= 0 ||
StochDPeriod <= 0 ||
StochSlowing <= 0 ||
BuyLevel < 0 || BuyLevel > 100 ||
SellLevel < 0 || SellLevel > 100 ||
BuyLevel >= SellLevel ||
ATRPeriod <= 0 ||
ATRMinPips < 0.0 ||
ATRMaxPips <= ATRMinPips ||
TakeProfitPips <= 0.0 ||
StopLossPips <= 0.0 ||
MaxSpreadPoints < 0 ||
BrokerMinDistancePips < 0.0 ||
TakeProfitPips + 1e-8 < BrokerMinDistancePips ||
StopLossPips + 1e-8 < BrokerMinDistancePips ||
RiskPercent <= 0.0)
{
Print("Invalid input parameters.");
return(INIT_PARAMETERS_INCORRECT);
}
g_lastBarTime = iTime(Symbol(), SignalTimeframe, 0);
double brokerStopLevelPips =
(MarketInfo(Symbol(), MODE_STOPLEVEL) * Point) / PipSize();
Print("AI_HighWin_Scalper_v1.00 initialized. Symbol=", Symbol(),
" TF=", SignalTimeframe,
" Digits=", Digits,
" Point=", DoubleToString(Point, Digits),
" PipSize=", DoubleToString(PipSize(), Digits),
" TP=", DoubleToString(TakeProfitPips, 1), " pips",
" SL=", DoubleToString(StopLossPips, 1), " pips",
" UserMinDistance=", DoubleToString(BrokerMinDistancePips, 1), " pips",
" BrokerStopLevel=", DoubleToString(brokerStopLevelPips, 1), " pips",
" MaxSpread=", MaxSpreadPoints, " points",
" Risk=", DoubleToString(RiskPercent, 2), "%");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick |
//+------------------------------------------------------------------+
void OnTick()
{
if(!IsNewSignalBar())
return;
if(!EnoughBars())
return;
// One market position per symbol + MagicNumber
if(HasOpenPosition())
return;
if(!SpreadIsAcceptable())
return;
int signal = GetEntrySignal(); // 1=BUY, -1=SELL, 0=NONE
if(signal == 0)
return;
OpenPosition(signal);
}
//+------------------------------------------------------------------+
//| Detect a new bar on the signal timeframe |
//+------------------------------------------------------------------+
bool IsNewSignalBar()
{
datetime currentBarTime = iTime(Symbol(), SignalTimeframe, 0);
if(currentBarTime <= 0)
return(false);
if(currentBarTime == g_lastBarTime)
return(false);
g_lastBarTime = currentBarTime;
return(true);
}
//+------------------------------------------------------------------+
//| Ensure enough history exists |
//+------------------------------------------------------------------+
bool EnoughBars()
{
int required = SlowEMAPeriod + 20;
int bars = iBars(Symbol(), SignalTimeframe);
if(bars < required)
{
Print("Not enough bars. Required=", required, " Current=", bars);
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Spread filter |
//| MaxSpreadPoints uses MT4 points. |
//| Example on 3/5-digit FX: 5 points = 0.5 pip. |
//+------------------------------------------------------------------+
bool SpreadIsAcceptable()
{
double spreadPoints = MarketInfo(Symbol(), MODE_SPREAD);
if(spreadPoints > MaxSpreadPoints)
{
Print("Spread filter: current=", DoubleToString(spreadPoints, 0),
" points, max=", MaxSpreadPoints);
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Entry logic |
//| Uses only closed bars (shift 1 and 2). |
//+------------------------------------------------------------------+
int GetEntrySignal()
{
//--- Trend filter: EMA50 vs EMA200 on the latest closed bar
double fastEMA = iMA(Symbol(), SignalTimeframe, FastEMAPeriod, 0,
MODE_EMA, PRICE_CLOSE, 1);
double slowEMA = iMA(Symbol(), SignalTimeframe, SlowEMAPeriod, 0,
MODE_EMA, PRICE_CLOSE, 1);
//--- Stochastic 5,3,3 / SMA / Low-High
double k1 = iStochastic(Symbol(), SignalTimeframe,
StochKPeriod, StochDPeriod, StochSlowing,
MODE_SMA, 0, MODE_MAIN, 1);
double d1 = iStochastic(Symbol(), SignalTimeframe,
StochKPeriod, StochDPeriod, StochSlowing,
MODE_SMA, 0, MODE_SIGNAL, 1);
double k2 = iStochastic(Symbol(), SignalTimeframe,
StochKPeriod, StochDPeriod, StochSlowing,
MODE_SMA, 0, MODE_MAIN, 2);
double d2 = iStochastic(Symbol(), SignalTimeframe,
StochKPeriod, StochDPeriod, StochSlowing,
MODE_SMA, 0, MODE_SIGNAL, 2);
//--- ATR volatility filter
double atrPrice = iATR(Symbol(), SignalTimeframe, ATRPeriod, 1);
double atrPips = atrPrice / PipSize();
if(atrPips < ATRMinPips || atrPips > ATRMaxPips)
return(0);
//--- BUY
// Uptrend + oversold pullback + bullish Stochastic cross
bool buySignal =
(fastEMA > slowEMA) &&
(k1 < BuyLevel) &&
(k2 <= d2) &&
(k1 > d1);
if(buySignal)
return(1);
//--- SELL
// Downtrend + overbought pullback + bearish Stochastic cross
bool sellSignal =
(fastEMA < slowEMA) &&
(k1 > SellLevel) &&
(k2 >= d2) &&
(k1 < d1);
if(sellSignal)
return(-1);
return(0);
}
//+------------------------------------------------------------------+
//| Check open positions for this symbol + MagicNumber |
//+------------------------------------------------------------------+
bool HasOpenPosition()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
if(OrderSymbol() != Symbol())
continue;
if(OrderMagicNumber() != MagicNumber)
continue;
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| Open a market position |
//+------------------------------------------------------------------+
bool OpenPosition(int signal)
{
RefreshRates();
int orderType;
double entryPrice;
double stopLoss;
double takeProfit;
double pip = PipSize();
if(signal > 0)
{
orderType = OP_BUY;
entryPrice = Ask;
stopLoss = entryPrice - StopLossPips * pip;
takeProfit = entryPrice + TakeProfitPips * pip;
}
else
{
orderType = OP_SELL;
entryPrice = Bid;
stopLoss = entryPrice + StopLossPips * pip;
takeProfit = entryPrice - TakeProfitPips * pip;
}
// Minimum TP/SL distance check.
// FXTF requires at least 5 pips; MODE_STOPLEVEL is also respected.
double brokerStopLevelPips =
(MarketInfo(Symbol(), MODE_STOPLEVEL) * Point) / pip;
double requiredMinPips = MathMax(BrokerMinDistancePips,
brokerStopLevelPips);
double actualSLPips = MathAbs(entryPrice - stopLoss) / pip;
double actualTPPips = MathAbs(entryPrice - takeProfit) / pip;
if(actualSLPips + 1e-8 < requiredMinPips ||
actualTPPips + 1e-8 < requiredMinPips)
{
Print("Order skipped: minimum distance not met.",
" Required=", DoubleToString(requiredMinPips, 1), " pips",
" ActualSL=", DoubleToString(actualSLPips, 1), " pips",
" ActualTP=", DoubleToString(actualTPPips, 1), " pips",
" MODE_STOPLEVEL=", DoubleToString(brokerStopLevelPips, 1), " pips");
return(false);
}
double lots = CalculateRiskLot(StopLossPips);
if(lots <= 0.0)
{
Print("Order skipped: calculated lot size is invalid.");
return(false);
}
entryPrice = NormalizeDouble(entryPrice, Digits);
stopLoss = NormalizeDouble(stopLoss, Digits);
takeProfit = NormalizeDouble(takeProfit, Digits);
ResetLastError();
int ticket = OrderSend(Symbol(),
orderType,
lots,
entryPrice,
SlippagePoints,
stopLoss,
takeProfit,
"AI_HighWin_Scalper_v1.00",
MagicNumber,
0,
(orderType == OP_BUY ? clrBlue : clrRed));
if(ticket < 0)
{
int err = GetLastError();
Print("OrderSend failed. Error=", err,
" Type=", orderType,
" Lots=", DoubleToString(lots, 2),
" Entry=", DoubleToString(entryPrice, Digits),
" SL=", DoubleToString(stopLoss, Digits),
" TP=", DoubleToString(takeProfit, Digits));
return(false);
}
Print("Order opened. Ticket=", ticket,
" Type=", (orderType == OP_BUY ? "BUY" : "SELL"),
" Lots=", DoubleToString(lots, 2),
" Entry=", DoubleToString(entryPrice, Digits),
" SL=", DoubleToString(stopLoss, Digits),
" TP=", DoubleToString(takeProfit, Digits),
" SLdist=", DoubleToString(MathAbs(entryPrice-stopLoss)/pip, 1), " pips",
" TPdist=", DoubleToString(MathAbs(entryPrice-takeProfit)/pip, 1), " pips");
return(true);
}
//+------------------------------------------------------------------+
//| Risk-based lot calculation |
//| RiskPercent is account-balance based. |
//+------------------------------------------------------------------+
double CalculateRiskLot(double stopLossPips)
{
double riskMoney = AccountBalance() * RiskPercent / 100.0;
if(riskMoney <= 0.0 || stopLossPips <= 0.0)
return(0.0);
double pip = PipSize();
double stopDistancePrice = stopLossPips * pip;
// MODE_TICKVALUE = money value of one tick for 1.0 lot.
// MODE_TICKSIZE = minimum price movement corresponding to that tick.
// Therefore: number of ticks to SL = price distance / tick size.
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
if(tickValue <= 0.0 || tickSize <= 0.0)
{
Print("Risk lot error: invalid tick data. TickValue=",
DoubleToString(tickValue, 8),
" TickSize=", DoubleToString(tickSize, Digits));
return(0.0);
}
double ticksToSL = stopDistancePrice / tickSize;
double lossPerLot = ticksToSL * tickValue;
if(lossPerLot <= 0.0)
return(0.0);
double rawLots = riskMoney / lossPerLot;
double lots = NormalizeLot(rawLots);
double expectedLoss = lossPerLot * lots;
double expectedRiskPct = 0.0;
if(AccountBalance() > 0.0)
expectedRiskPct = expectedLoss / AccountBalance() * 100.0;
Print("Risk lot calc: Balance=", DoubleToString(AccountBalance(), 2),
" RiskMoney=", DoubleToString(riskMoney, 2),
" SL=", DoubleToString(stopLossPips, 1), " pips",
" TickValue=", DoubleToString(tickValue, 8),
" TickSize=", DoubleToString(tickSize, Digits),
" LossPerLot=", DoubleToString(lossPerLot, 2),
" RawLots=", DoubleToString(rawLots, 4),
" Lots=", DoubleToString(lots, 2),
" ExpectedLoss=", DoubleToString(expectedLoss, 2),
" ExpectedRisk=", DoubleToString(expectedRiskPct, 3), "%");
return(lots);
}
//+------------------------------------------------------------------+
//| Normalize lot to broker limits |
//+------------------------------------------------------------------+
double NormalizeLot(double lots)
{
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
if(minLot <= 0.0)
minLot = 0.01;
if(maxLot <= 0.0)
maxLot = minLot;
if(lotStep <= 0.0)
lotStep = 0.01;
// Do not round a risk-based position UP to the broker minimum.
// If the requested risk is too small for the minimum lot, skip the trade.
if(lots + 1e-12 < minLot)
{
Print("Risk lot skipped: calculated lot is below broker minimum.",
" RawLots=", DoubleToString(lots, 4),
" MinLot=", DoubleToString(minLot, 4));
return(0.0);
}
if(lots > maxLot)
lots = maxLot;
lots = MathFloor((lots + 1e-12) / lotStep) * lotStep;
if(lots + 1e-12 < minLot)
return(0.0);
int lotDigits = 2;
if(lotStep >= 1.0)
lotDigits = 0;
else if(lotStep >= 0.1)
lotDigits = 1;
else if(lotStep >= 0.01)
lotDigits = 2;
else
lotDigits = 3;
return(NormalizeDouble(lots, lotDigits));
}
//+------------------------------------------------------------------+
//| Pip size helper |
//| 5-digit / 3-digit FX: 1 pip = 10 points |
//+------------------------------------------------------------------+
double PipSize()
{
if(Digits == 3 || Digits == 5)
return(Point * 10.0);
return(Point);
}
//+------------------------------------------------------------------+
過去検証|勝率95%のEAは長期では通用しなかった
ここからは、この記事を最初に公開した2020年当時の高勝率EA検証です。
当時は「高勝率EAはどうやって作るのか?」という疑問から、ストキャスティクスとRSIを使った逆張りEAを作成しました。
利確2.5pips・損切り17pipsという損大利小の設定で、2005年1月10日~2006年10月10日の5分足をバックテストしたところ、驚くほど良い結果になりました。

| 項目 | 旧EAの結果 |
|---|---|
| 期間 | 2005年1月10日~2006年10月10日 |
| 時間足 | M5 |
| 取引数 | 37,021回 |
| 勝率 | 約95% |
| PF | 2.72 |
| 平均利確 | 約2.5pips |
| 平均損失 | 約17pips |
数字だけ見ると、今回のV1よりも旧EAの方が圧倒的に優秀です。取引数も37,021回あり、当時はかなり期待できるように見えました。
ところが、2006年10月以降は同じEAがほとんど通用しなくなりました。

勝率も約95%から約88%へ下がりました。88%だけを見るとまだ高勝率ですが、利小損大のEAではこの数%の低下が大きく効きます。
つまり、「勝率が高い」という一点だけではEAの強さは判断できないということです。
高勝率EAを作るときに大切だと感じたこと
旧EAの検証と今回のV1を比べると、EA作りで大切なのは「一番きれいなバックテストを作ること」ではないと感じます。
今回も開発期間だけならPF1.20でしたが、未使用期間を含めると通年PFは1.18でした。そこで数値を戻すために追加調整するのではなく、未使用期間でも大きく崩れなかった状態をそのまま採用しています。
高勝率EAは、TPを小さくしてSLを大きくするだけでも勝率を上げられます。
ただし本当に重要なのは、勝率だけでなくPF・ドローダウン・取引数を確認し、さらに調整に使っていない期間でも同じ考え方が通用するかを見ることです。
まとめ|スキャルピングEAは作った後の検証が重要
今回のAIスキャルピングEA V1は、USDJPYの5分足を対象に、EMA・ストキャスティクス・ATRを組み合わせて作成しました。
最終結果は698取引・勝率85.39%・PF1.18・最大DD2.89%です。
旧EAのような勝率95%・PF2.72という派手な数字ではありませんが、開発期間とは別に未使用期間を設け、スプレッドを変えた確認も行いました。
EAを自作するときは、「もっと勝率を上げる」だけでなく、条件を一つずつ変えて何が効いているのか確認することが大切です。今回のソースコードも、EMAやストキャスティクスの数値を変更しながらバックテストすると、ロジックと成績の関係がわかりやすいと思います。
他のEAサンプルやロジックも確認したい方は、EAソースコード一覧から目的に合うサンプルを探せます。







コメント