均線交叉策略完整實作:從訊號到下單的完整 MQL5 流程

📌 本文重點
從頭到尾實作一個完整可用的均線交叉(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 撰寫。內容僅供教育目的,不構成投資建議。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *