均線交叉策略完整實作:從訊號到下單的完整 MQL5 流程
📌 本文重點
從頭到尾實作一個完整可用的均線交叉(MA Cross)EA:雙均線訊號、動態手數、止損止盈、持倉管理,以及策略測試器優化設定。
從頭到尾實作一個完整可用的均線交叉(MA Cross)EA:雙均線訊號、動態手數、止損止盈、持倉管理,以及策略測試器優化設定。
策略邏輯
- 買入:快線(10期 SMA)從下穿越慢線(30期 SMA)(金叉)
- 賣出:快線從上穿越慢線(死叉)→ 同時平掉多單
- 止損:2 × ATR
- 止盈:4 × ATR(風險報酬比 1:2)
- 手數:固定風險 1% 帳戶餘額
完整 EA 程式碼
#include <Trade\Trade.mqh>
#property version "1.00"
input int InpFastPeriod = 10; // 快線週期
input int InpSlowPeriod = 30; // 慢線週期
input int InpATRPeriod = 14; // ATR 週期
input double InpATRMult = 2.0; // ATR 止損倍數
input double InpRiskPct = 1.0; // 每筆風險 %
input int InpMagic = 20260101;
CTrade g_trade;
int g_fastHandle = INVALID_HANDLE;
int g_slowHandle = INVALID_HANDLE;
int g_atrHandle = INVALID_HANDLE;
datetime g_lastBar = 0;
int OnInit()
{
g_fastHandle = iMA(_Symbol, _Period, InpFastPeriod, 0, MODE_SMA, PRICE_CLOSE);
g_slowHandle = iMA(_Symbol, _Period, InpSlowPeriod, 0, MODE_SMA, PRICE_CLOSE);
g_atrHandle = iATR(_Symbol, _Period, InpATRPeriod);
if (g_fastHandle==INVALID_HANDLE || g_slowHandle==INVALID_HANDLE || g_atrHandle==INVALID_HANDLE)
return INIT_FAILED;
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviations(10);
return INIT_SUCCEEDED;
}
void OnDeinit(const int r)
{
IndicatorRelease(g_fastHandle);
IndicatorRelease(g_slowHandle);
IndicatorRelease(g_atrHandle);
}
void OnTick()
{
// 只在新K線執行
datetime curBar = iTime(_Symbol, _Period, 0);
if (curBar == g_lastBar) return;
g_lastBar = curBar;
// 確保足夠數據
if (BarsCalculated(g_fastHandle) < InpSlowPeriod + 2) return;
// 取得指標值(使用已完成K線 index=1 和 2)
double fast[3], slow[3], atr[2];
ArraySetAsSeries(fast, true); ArraySetAsSeries(slow, true); ArraySetAsSeries(atr, true);
if (CopyBuffer(g_fastHandle, 0, 0, 3, fast) < 3) return;
if (CopyBuffer(g_slowHandle, 0, 0, 3, slow) < 3) return;
if (CopyBuffer(g_atrHandle, 0, 0, 2, atr) < 2) return;
// 計算訊號(使用 index[1] 和 [2],避免未來函數)
bool goldenCross = fast[2] <= slow[2] && fast[1] > slow[1];
bool deathCross = fast[2] >= slow[2] && fast[1] < slow[1];
int positions = CountPositions();
// 金叉 + 無持倉 → 買入
if (goldenCross && positions == 0)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double sl = ask - atr[1] * InpATRMult;
double tp = ask + atr[1] * InpATRMult * 2;
int slPips = (int)(atr[1] * InpATRMult / pt);
double lots = CalcLots(slPips);
if (g_trade.Buy(lots, _Symbol, ask, NormalizeDouble(sl, _Digits), NormalizeDouble(tp, _Digits), "MA金叉"))
Print("買入成功 @ ", ask, " SL=", sl, " TP=", tp, " Lots=", lots);
}
// 死叉 + 有多單 → 平倉
if (deathCross && positions > 0)
{
CloseAllPositions();
Print("死叉平倉");
}
}
int CountPositions()
{
int n = 0;
for (int i = PositionsTotal()-1; i >= 0; i--)
if (PositionGetSymbol(i)==_Symbol && PositionGetInteger(POSITION_MAGIC)==InpMagic) n++;
return n;
}
void CloseAllPositions()
{
for (int i = PositionsTotal()-1; i >= 0; i--)
{
if (PositionGetSymbol(i)!=_Symbol) continue;
if (PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
g_trade.PositionClose(PositionGetInteger(POSITION_TICKET));
}
}
double CalcLots(int slPips)
{
double riskAmt = AccountInfoDouble(ACCOUNT_BALANCE) * InpRiskPct / 100.0;
double tickVal = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSz = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double pointVal = tickVal * (tickSz / SymbolInfoDouble(_Symbol, SYMBOL_POINT));
if (pointVal <= 0 || slPips <= 0) return 0.01;
double lots = riskAmt / (slPips * pointVal);
double minL = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxL = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
lots = MathMax(MathMin(lots, maxL), minL);
return NormalizeDouble(MathRound(lots/step)*step, 2);
}
// 策略測試器評分
double OnTester()
{
double profit = TesterStatistics(STAT_PROFIT);
double dd = TesterStatistics(STAT_BALANCE_DD);
double trades = TesterStatistics(STAT_TRADES);
double pf = TesterStatistics(STAT_PROFIT_FACTOR);
if (trades < 20 || pf < 1.2) return 0;
return (dd > 0) ? profit / dd : 0;
}
回測建議設定
- 品種:EURUSD、GBPUSD(流動性高,點差小)
- 時間框架:H1 或 H4
- 模式:每個即時報價(基於真實跳動點)
- 期間:2020–2024(至少 3–4 年)
本文由 James Lee 撰寫。內容僅供教育目的,不構成投資建議。